Material Design是由谷歌的设计工程师们基于传统优秀的设计原则,结合丰富的创意和科学技术所发明的一套全新的界面设计语言,包含了视觉、运动、互动效果等特性,Material Design最大的特点就是好看,它的出现使得Android首次在UI方面超越了iOS。
Material Design是一个推荐的设计规范,主要面向UI设计人员,而不是面向开发者。Google在2015年的Google I/O大会上推出了一个Design Support库,这个库将Material Design中最具代表性的一些控件和效果进行了封装,使开发者在即使不了解Material Design的情况下也能非常轻松地将自己的应用Material化。
Design Support库中常用的控件如下:
文章目录
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
NavigationView(导航视图)通常与DrawerLayout(抽屉布局)结合使用,实现了良好的侧滑交互体验。
使用NavigationView需要在build.gradle文件中引入相应的库
//androidx库
dependencies {
...
implementation 'com.google.android.material:material:1.1.0'
}
//support库
dependencies {
...
compile 'com.android.support:design:24.2.1'
}
在布局文件中引入如下代码即可使用
<!--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 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 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
}
在布局文件中引入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文件中的主题名称