Material Design是由谷歌的设计工程师们基于传统优秀的设计原则,结合丰富的创意和科学技术所发明的一套全新的界面设计语言,包含了视觉、运动、互动效果等特性,Material Design最大的特点就是好看,它的出现使得Android首次在UI方面超越了iOS。

Material Design是一个推荐的设计规范,主要面向UI设计人员,而不是面向开发者。Google在2015年的Google I/O大会上推出了一个Design Support库,这个库将Material Design中最具代表性的一些控件和效果进行了封装,使开发者在即使不了解Material Design的情况下也能非常轻松地将自己的应用Material化。

Toolbar

Toolbar不仅继承了ActionBar的所有功能,而且灵活性很高,可以配合其他控件来完成一些Material Design的效果

新建项目时都会默认使用ActionBar,而这个ActionBar来自项目指定的主题,要使用Toolbar,则需要指定一个不带ActionBar的主题,在 res/values/styles.xml 文件中修改:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

parent主题是Theme.AppCompat.Light.DarkActionBar,这是一个深色的ActionBar主题,将其指定成Theme.AppCompat.NoActionBar(深色主题,界面的主体颜色为深色)或Theme.AppCompat.Light.NoActionBar(淡色主题,界面的主体颜色为淡色,陪衬颜色为深色),即可获得不带ActionBar的主题。

可以通过重写属性来实现界面颜色的定制,在<style>标签中添加要重写的属性,属性值以及每个属性所指代的区域如下图所示:
属性和指代区域

colorAccent不只是用来指定这样一个按钮的颜色,而是更多表达了一个强调的意思,例如一些控件的选中状态也会使用colorAccent的颜色

向布局文件中添加如下代码即可引入Toolbar:

<!--support库-->
<android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

<!--androidx库-->
<androidx.appcompat.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
  • android:theme:指定Toolbar主题
  • app:popupTheme:指定Toolbar中弹出菜单的主题

由于Material Design是在Android 5.0系统中才出现的,而很多的Material属性在5.0之前的系统
中并不存在,那么为了能够兼容之前的老系统,需要指定xmlns:app,使用app:attribute的形式进行属性设置。
在Activity中添加如下代码可以使Toolbar的外观和功能与ActionBar一致

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

可以向Toolbar中添加各种action按钮,同时,向可以通过设置app:showAsAction属性来指定按钮的显示位置

<menu 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/backup"
        android:icon="@drawable/ic_backup"
        android:title="Backup"
        app:showAsAction="always" />
</menu>

app:showAsAction可选值有如下几个:

  • always:永远显示在Toolbar中,如果屏幕空间不够则不显示
  • ifRoom:屏幕空间足够的情况下显示在Toolbar中,不够的话就显示在菜单当中
  • never:永远显示在菜单当中。

Toolbar中的action按钮只会显示图标,菜单中的action按钮只会显示文字

滑动菜单

所谓的滑动菜单就是将一些菜单选项隐藏起来,而不是放置在主屏幕上,然后可以通过滑动的方式将菜单显示出来。这种方式既节省了屏幕空间,又实现了非常好的动画效果

DrawerLayout

DrawerLayout是一个布局,在布局中允许放入两个直接子控件,第一个子控件是主屏幕中显示的内容,第二个子控件是滑动菜单中显示的内容。

<!--support库-->
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        ···
    </FrameLayout>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:text="This is menu"
        android:textSize="30sp"
        android:background="#FFF" />
</android.support.v4.widget.DrawerLayout>

<!--androidx库-->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        ···
    </FrameLayout>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:text="This is menu"
        android:textSize="30sp"
        android:background="#FFF" />

</androidx.drawerlayout.widget.DrawerLayout>

在第二个子控件中,layout_gravity 这个属性必须指定,可选值如下:

  • left:滑动菜单在左边
  • right:滑动菜单在右边
  • start:根据系统语言,决定滑动菜单位置(系统语言是从左往右的,比如英语、汉语,滑动菜单就在左边,如果系统语言是从右往左的,比如阿拉伯语,滑动菜单就在右边)

一般来说,用户并不知道这个滑动菜单的存在,需要Toolbar最左边加入一个导航按钮,这样就可以用两种方式来打开滑动菜单

public class MainActivity extends AppCompatActivity {
    private DrawerLayout drawerLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            //让导航按钮显示
            actionBar.setDisplayHomeAsUpEnabled(true);
            //设置导航按钮图标
            actionBar.setHomeAsUpIndicator(R.mipmap.ic_launcher);
        }
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                drawerLayout.openDrawer(GravityCompat.START);
                break;
            default:
        }
        return true;
    }
}
  • setDisplayHomeAsUpEnabled():使导航按钮显示
  • setHomeAsUpIndicator():设置导航图标的图标
  • openDrawer():显示滑动菜单,传入Gravity参数,一般设置与XML中定义一致
  • Toolbar最左侧的按钮叫做HomeAsUp按钮,默认是一个返回箭头,含义是返回上一个活动
  • HomeAsUp按钮的id为android.R.id.home

NavigationView(导航视图)通常与DrawerLayout(抽屉布局)结合使用,实现了良好的侧滑交互体验。

使用NavigationView需要在build.gradle文件中引入相应的库

//androidx库
dependencies {
    ...
    implementation &#39;com.google.android.material:material:1.1.0&#39;
}

//support库
dependencies {
    ...
    compile &#39;com.android.support:design:24.2.1&#39;
}

在布局文件中引入如下代码即可使用

<!--androidx库-->
<com.google.android.material.navigation.NavigationView
    android:id="@+id/nav_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    app:headerLayout="@layout/nav_header"
    app:menu="@menu/nav_menu" />

<!--support库-->
<android.support.design.widget.NavigationView
    android:id="@+id/nav_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    app:menu="@menu/nav_menu"
    app:headerLayout="@layout/nav_header "/>
  • app:menu:向NavigationView添加菜单项布局文件
  • app:headerLayout:向NavigationView添加头部布局文件,例如头像和名称的布局

NavigationView还可以添加菜单项的点击事件

NavigationView navView = (NavigationView) findViewById(R.id.nav_view);
navView.setCheckedItem(R.id.nav_call);
navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        //相关逻辑
    }
});

悬浮按钮与可交互提示

FloatingActionButton

FloatingActionButton能够轻松实现悬浮按钮的效果
在布局文件中引入如下代码即可

<!--androidx库-->
<com.google.android.material.floatingactionbutton.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:layout_margin="16dp"
    app:elevation="8dp"
    app:layout_anchor="@id/appBar"
    android:src="@mipmap/ic_launcher"/>

<!--support库-->
<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:layout_margin="16dp"
    app:elevation="8dp"
    app:layout_anchor="@id/appBar"
    android:src="@drawable/ic_done" />
  • app:elevation:指定一个高度值,高度值越大,投影范围也越大,投影效果越淡;高度值越小,投影范围也越小,投影效果越浓
  • app:layout_anchor:指定一个锚点,表示悬浮按钮出现的区域

效果图如下:
FloatingActionButton

FloatingActionButton同样可以设置监听事件,与普通按钮并没有差别

FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //相关逻辑
    }
});

Snackbar

Snackbar与Toast类似,两者互为补充。两者之间有着不同的应用场景:Toast的作用是告诉用户现在发生了什么事情,用户只能被动接收这个事情;Snackbar则在这方面进行了扩展,它允许在提示当中加入一个可交互按钮,当用户点击按钮的时候可以执行一些额外的逻辑操作
Snackbar使用十分简单,与Toast类似:用静态方法make()创建Snackbar对象,调用asetAction()方法设置交互事件,最后用show()方法让Snackbar显示

public static Snackbar make(@NonNull View view, @NonNull CharSequence text, @Duration int duration);
  • View view:当前界面布局的任意一个View都可以,Snackbar会使用这个View来自动查找最外层的布局,用于展示Snackbar
  • CharSequence text:要显示的文本内容
  • int duration:显示的时长,有三个内置常量可以选择:Snackbar.LENGTH_LONG 、Toast.LENGTH_SHORT和LENGTH_INDEFINITE
public Snackbar setAction(@Nullable CharSequence text, @Nullable final View.OnClickListener listener)
  • CharSequence text:交互按钮的显示名称
  • View.OnClickListener listener:点击交互按钮的监听事件

CoordinatorLayout

CoordinatorLayout是一个加强版的FrameLayout,它可以监听其所有子控件的各种事件,然后自动做出最为合理的响应,它有两个作用:

  • 用作应用的顶层布局管理器
  • 通过为子View指定 behavior 实现自定义的交互行为

可以在CoordinatorLayout中的子控件中添加app:layout_behavior属性来指定布局行为
使用以下代码引入CoordinatorLayout

<!--androidx库-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</androidx.coordinatorlayout.widget.CoordinatorLayout>

<!--support库-->
<android.support.design.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</android.support.design.widget.CoordinatorLayout>

卡片式布局

CardView

CardView是用于实现卡片式布局效果的重要控件,是一个FrameLayout,但额外提供了圆角和阴影等效果,看上去会有立体的感觉

<!--androidx库-->
<androidx.cardview.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    app:cardCornerRadius="4dp">

</androidx.cardview.widget.CardView>

<!--support库-->
<android.support.v7.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="4dp"
    app:elevation="5dp">

</android.support.v7.widget.CardView>
  • app:cardCornerRadius:指定定卡片圆角的弧度,数值越大,圆角的弧度也越大
  • app:elevation:指定卡片的高度,高度值越大,投影范围也越大,投影效果越淡;高度值越小,投影范围也越小,投影效果越浓

CardView中的控件会显示在一张卡片上,一般CardView会配合RecyclerView一块使用,即在RecyclerView中引用CardView,呈现卡片式布局

AppBarLayout

AppBarLayout实际上是一个垂直方向的LinearLayout,它在内部做了很多滚动事件的封装,并应用了一些Material Design的设计理念。

AppBarLayout必须是CoordinatorLayout子控件。当AppBarLayout接收到滚动事件(在同一个CoordinatorLayout中的其他子控件的滚动事件)的时候,它内部的子控件其实是可以指定如何去影响这些事件的,使用app:layout_scrollFlags属性来定制,这个属性有以下几个可选值:

  • scroll:向上滚动时,控件跟着一起向上滚动并实现隐藏
  • enterAlways:向下滚动时,控件跟着一起向下滚动并重新显示
  • snap:控件还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示

引入AppBarLayout

<!--androix库-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </com.google.android.material.appbar.AppBarLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>


<!--support库-->
<android.support.design.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </android.support.design.widget.AppBarLayout>

</android.support.design.widget.CoordinatorLayout>

下拉刷新

SwipeRefreshLayout是用于实现下拉刷新功能的核心类,把想要实现下拉刷新功能的控件放置到SwipeRefreshLayout中,就可以迅速让这个控件支持下拉刷新
向项目中引入SwipeRefreshLayout(使用androidx库时需要引入)

//androidx库
dependencies {
    ...
    implementation &#39;androidx.swiperefreshlayout:swiperefreshlayout:1.0.0&#39;
}

在布局文件中引入SwipeRefreshLayout

<!--androidx库-->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    android:id="@+id/swipe_refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

<!--support库-->
<android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/swipe_refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</android.support.v4.widget.SwipeRefreshLayout>

引入SwipeRefreshLayout还需要添加相关的刷新逻辑:

SwipeRefreshLayout swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
//设置刷新进度条颜色
swipeRefresh.setColorSchemeResources(R.color.colorPrimary);
//设置刷新***
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
    @Override
    public void onRefresh() {
        //刷新逻辑
    }
});
//用于表示刷新事件结束,并隐藏进度条
swipeRefresh.setRefreshing(false);

可折叠式标题栏

CollapsingToolbarLayout是一个作用于Toolbar基础之上的布局,它可以让Toolbar的效果变得更加丰富,不仅仅是展示一个标题栏,而是能够实现非常华丽的效果。

CollapsingToolbarLayout是不能独立存在的,它在设计的时候就被限定只能作为AppBarLayout的直接子布局来使用。

在布局文件中引用:

<!--androidx库-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBar"
        android:layout_width="match_parent"
        android:layout_height="250dp">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

        </com.google.android.material.appbar.CollapsingToolbarLayout>

    </com.google.android.material.appbar.AppBarLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

<!--support库-->
<android.support.design.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBar"
        android:layout_width="match_parent"
        android:layout_height="250dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">
        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

</android.support.design.widget.CoordinatorLayout>
  • app:contentScrim:指定CollapsingToolbarLayout在趋于折叠状态以及折叠之后的背景色。CollapsingToolbarLayout在折叠之后就是一个普通的Toolbar,背景色是colorPrimary

使用app:layout_collapseMode属性指定CollapsingToolbarLayout的子控件(该属性写在子控件上)在CollapsingToolbarLayout折叠过程中的折叠模式,有可选值:

  • pin:在折叠的过程中位置始终保持不变
  • parallax:在折叠的过程中产生一定的错位偏移

开发Tips

在Android 5.0系统之前,无法对状态栏的背景或颜色进行操作的,那个时候还没有Material Design的概念。是Android 5.0及之后的系统都是支持这个功能的。

想要让背景图能够和系统状态栏融合,需要借助android:fitsSystemWindows这个属性来实现,将控件的android:fitsSystemWindows属性指定成true,就表示该控件会出现在系统状态栏里,需要将该控件的父布局android:fitsSystemWindows属性指定成true

除了指定android:fitsSystemWindows这个属性,还需要设定程序的主题色为透明
对于Android 5.0以上系统,需要在res目录下创建一个values-v21目录,在该目录下创建styles.xml文件,内容如下:

<resources>
    <style name="MyTheme" parent="AppTheme">
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>
</resources>

对于Android 5.0以下系统,在values/styles.xml文件中添加以下内容:

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="MyTheme" parent="AppTheme">

    </style>

</resources>

最后在AndroidManifest.xml中将想要实现这个效果的Activity添加android:theme属性,属性值为styles.xml文件中的主题名称