1. 什么是 Material Design
定义:
Material Design
是 Google 在 推出的一套全新的界面设计语言。它基于传统优秀的设计原则、结合丰富的创意和科技,包含了视觉、运动、交互效果等特性发展
- 2014 年 Google I/O 大会, Google 推出
Material Design
- 从 Android 5.0系统开始,Google 将所有内置应用都使用
Material Design
风格进行设计 - 2015 年 Google I/O 大会,Google 推出了一个 Design Support 库,这个库将
Material Design
中最具代表性的一些控件和效果进行了封装,使得开发者可以轻松地将自己的应用Material
化
- 2014 年 Google I/O 大会, Google 推出
2. 怎样理解 Toolbar
ActionBar
和Toolbar
的对比- 每个活动最顶部的那个标题栏就是
ActionBar
,由于自身设计的原因,ActionBar
只能位于活动的顶部,不能实现一些Material Design
的效果。官方不再建议使用ActionBar
,而是更加推荐使用Toolbar
Toolbar
不仅继承了ActionBar
的所有功能,而且灵活性很高,和可以配合其他控件来完成一些Material Design
的效果- 在
Fragment
中最好不要直接使用Toolbar
或者ActionBar
,复用的时候可能会出现意想不到的效果
- 每个活动最顶部的那个标题栏就是
主题
Theme.AppCompat.NoActionBar
和Theme.AppCompat.Light.NoActionBar
的区别- 在
AndroidManifest.xml
文件的application
标签下,有一个theme
属性,这个属性指定了一个AppTheme
的主题,资源文件中这个AppTheme
主题的parent
默认是Theme.AppCompat.Light.DarkActionBar
Theme.AppCompat.NoActionBar
:表示深色主题,它会将界面的主题颜色设为深色,陪衬颜色设为淡色Theme.AppCompt.Light.NoActionBar
:表示淡色主题,它会将界面的主题颜色设为淡色,陪衬颜色设为深色
- 在
AppTheme
中的几个属性分别什么意思colorPrimary
:应用标题栏的颜色colorPrimaryDark
:系统状态栏的颜色textColorPrimary
:应用标题栏中文字的颜色windowBackground
:应用界面背景颜色navigationBarColor
:应用底部导航栏颜色colorAccent
:不只是用来指定一个按钮的颜色(比如FAB
),更多表达一个强调的意思,比如一些控件的选中状态也会使用colorAccent
的颜色
解释一下
activity_main.xml
布局文件中Toobar
标签主要属性的作用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
// 布局文件中指定一个 xmlns:app 的命名空间是因为:
// Material Design 是在 Android 5.0 系统中才出现的,很多的 Material 属性在 5.0 之前的系统中并不存在
// 为了兼容之前的老系统,就不能使用 android:attribute 这样的写法了,而是应该使用 app:attribute
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.v7.widget.Toolbar // Toolbar 控件是 appcompat-v7 库提供的
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" // 高度设置为 actionBar 的高度
android:background="?arrr/colorPrimary" // 背景色
// 前提是在 style.xml 中将程序的主题指定了淡色主题
// 所以 Toolbar 上面的各种元素就会自动使用深色系,这是为了和主体颜色区分开
// 但是这个效果很差,之前使用 ActionBar 时文字都是白色的,现在变成黑色会很难看
// 为了能让 Toolbar 单独使用深色主题,而不受 styles.xml 中程序主题影响,这里使用 android:theme 属性进行单独制定
// 但这也单独制定后又有新问题,如果 Toolbar 中有菜单项,那么弹出的菜单项也会变成深色主题,这样就再次变得很难看
// 所以这里使用了 app:popupTheme 属性,将单独弹出的菜单项指定成了淡色主题
android:theme="@style/ThemeOverlay.AppCompat.DarkActionBar" // 即,影响标题栏内文字的颜色
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> // 即,影响标题栏内菜单项的颜色
</FrameLayout>
// 使用 Toolbar
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);解释下面
action
按钮布局文件toolbar.xml
中item
标签主要属性的作用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> // 使用 app 命名空间兼容低版本系统
<item // 使用 <item> 标签定义 action 按钮,Toolbar 中的 action 按钮只会显示图标、菜单中的 action 按钮只会显示文字
android:id="@+id/backup"
android:icon="@drawable/ic_backup"
android:title="Backup"
app:showAsAction="always" /> // always 表示永远显示在 Toolbar 中,如果屏幕空间不够则不显示
<item // 使用 <item> 标签定义 action 按钮,Toolbar 中的 action 按钮只会显示图标、菜单中的 action 按钮只会显示文字
android:id="@+id/delete"
android:icon="@drawable/ic_delete"
android:title="Delete"
app:showAsAction="ifRoom" /> // ifRoom 表示屏幕空间足够的情况下显示在 Toolbar 中,不够的话就显示在菜单中
<item // 使用 <item> 标签定义 action 按钮,Toolbar 中的 action 按钮只会显示图标、菜单中的 action 按钮只会显示文字
android:id="@+id/settings"
android:icon="@drawable/ic_settings"
android:title="Settings"
app:showAsAction="never" /> // never 表示不显示在 Toolbar 中,只显示在菜单中
</menu>
// 使用 toolbar.xml
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar, menu);
return true;
}
3. 使用 DrawerLayout
和 NavigationView
实现一个滑动菜单 Demo
MainActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42public class MainActivity extends AppCompatActivity {
private Toolbar mToolbar;
private DrawerLayout mDrawerLayout;
private NavigationView mNavView; //使用 NavigationView 需要在 app/build.gradle 文件的 dependencies 闭包中添加依赖
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
mDrawableLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mNavView = (NavigationView) findVieById(R.id.nav_view);
ActionBar actionBar = getSupportActionBar(); // 这里这个 ActionBar 实例的具体实现是由 Toolbar 来完成的
if(actionBar != null) { // 在 Toolbar 的最左边加上一个导航按钮,实际上这个按钮叫作 HomeAsUp 按钮,默认图标是一个返回的箭头
actionBar.setDisplayHomeAsUpEnabled(true); // 让导航按钮显示出来
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu); // 设置导航按钮图标
}
mNavView.setCheckedItem(R.id.nav_call); // 将 Call 菜单项设置为默认选中
mNavView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { // 设置菜单项选中的监听器
@Override
public boolean onNavigationItemSelected(MenuItem item) {
mDrawerLayout.closeDrawers(); // 将滑动菜单关闭
return true;
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: // HomeAsUp 按钮的 id 永远都是 android.R.id.home
// 将滑动菜单展示出来,方法要求传入一个 Gravity 参数,这里为了保证行为和 XML 中定义的一致,传入 GravityCompat.START
mDrawerLayout.openDrawer(GravityCompat.START);
break;
default:
break;
}
return true;
}
}activity_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37<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">
<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" />
</FrameLayout>
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
// 这个属性必须要指定,我们需要告诉 DrawerLayout 滑动菜单是在屏幕的左边还是右边
// 指定 left 表示滑动菜单在左边,指定 right 表示滑动菜单在右边
// 指定 start 表示会根据系统语言进行判断,如果系统语言是从左往右的,比如英语、汉语,滑动菜单就在左边;如果系统语言是从右往左的,比如阿拉伯语,滑动菜单就在右边
android:layout_gravity="start"
// 在 nav_menu.xml 布局文件标签 <menu> 中嵌套了一个 <group> 标签,然后将 group 的 checkableBehavior 属性指定为 single
// group 表示一个组,checkableBehavior 指定为 single 表示组中所有菜单项只能单选
app:menu="@menu/nav_menu"
app:headerLayout="@layout/nav_header" /> // 将 nav_header.xml 文件的高度设为 180dp,这是一个 NavigationView 比较适合的高度
</android.support.v4.widget.DrawerLayout>
4. View 的 elevation
属性的作用是
elevation
属性可以给View
指定一个高度值,高度值越大,投影范围也越大,但是投影效果越淡;高度值越小,投影范围也越小,但投影效果越浓FloatingActionButton
中有这个属性的默认值,可以看到投影/阴影效果
5. Snackbar
和 Toast
的区别
Snackbar
不是Toast
的替代品,它们两者之间有着不同的应用场景Toast
: 告诉用户现在发生了什么事情,但同时用户只能被动接收这个事情,用户不能做出选择Snackbar
: 对交互逻辑进行了扩展,不管是出现还是消失都是带有动画效果的,而且允许在提示当中加入一个可交互按钮,当用户点击按钮的时候可以执行一些额外的逻辑操作
6. 使用 Snackbar
实现一个简单的 Demo
1 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); |
7. 怎样理解 CoordinatorLayout
CoordinatorLayout
是一个加强版的FrameLayout
CoordinatorLayout
可以监听其所有子控件的各种事件,然后自动帮我们做出最为合理的响应,比如防止子控件之间的遮挡、子控件之间动画同步等
8. 怎样理解 CardView
CardView
是用于实现卡片式布局效果的重要控件,由appcompat-v7
提供CardView
也是一个FrameLayout
,只是额外提供了圆角和阴影等效果
9. 怎样理解 AppBarLayout
AppBarLayout
实际上是一个垂直方向的LinearLayout
AppBarLayout
在内部做了很多滚动事件的封装,并应用了一些Material Design
的设计理念
10. 使用 CoordinatorLayout
和 AppBarLayout
等实现一个滚动流畅的 Demo
1 | <android.support.v4.widget.DrawerLayout |
11. 怎样理解 SwipeRefreshLayout
SwipeRefreshLayout
是用于实现下拉刷新功能的核心类,由support-v4
提供在布局文件中,如果在
RecyclerView
的外面嵌套了一层SwipeRefreshLayout
,这样RecyclerView
就自动拥有下拉刷新功能了需要注意的是,由于
RecyclerView
现在变成了SwipeRefreshLayout
的子控件,因此之前使用app:layout_behavior
声明的布局行为现在也要移到SwipeRefreshLayout
中才行Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41public class MainActivity extends AppCompatActivity {
private SwipeRefreshLayout mSwipeRefresh;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSwipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
mSwipeRefresh.setColorSchemeResources(R.color.colorPrimary); // 设置下拉刷新进度条颜色
// 设置下拉刷新监听器
mSwipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
pubic void onRefresh() {
refreshFruits();
}
});
}
private void refreshFruits() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch(InterruptedException e) {
e.printStackTrace();
}
// 将线程切回主线程
runOnUiThread(new Runnable() {
@Override
public void run() {
initFruits();
adapter.notifyDataSetChanged(); // 通知数据发生了变化
swipeRefresh.setRefreshing(false); // 刷新事件结束,并隐藏刷新进度条
}
});
}
}).start();
}
}
12. 怎样理解 CollapsingToolbarLayout
CollapsingToolbarLayout
是一个作用于Toolbar
基础之上的布局,也是由Design Support
库提供的,可以实现可折叠式标题栏的效果CollapsingToolbarLayout
是不能独立存在的,它在设计的时候就被限定只能作为AppBarLayout
的直接子布局来使用,而AppBarLayout
又必须是CoordinatorLayout
的子布局Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" //Material Design 开发时会用到 xmlns:app 命名空间
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true" >
// 标题栏部分
<android.support.design.widget.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="250dp" // 高度指定为 250dp,视觉效果较好
android:fitsSystemWindows="true" >
// 调用 CollapsingToolbarLayout 的 setTitle() 方法设置页面标题
<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" // 这里要实现更加高级的 Toolbar 效果,因此需要将这个主题的指定从 Toolbar 提到上一层来
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary" // 指定 CollapsingToolbarLayout 在趋于折叠状态以及折叠之后的背景色
// 同上,也从 Toolbar 中移到上一层
// scroll 表示 CollapsingToolbarLayout 会随着内容详情的滚动一起滚动
// exitUntilCollapsed 表示当 CollapsingToolbarLayout 随着滚动完成折叠之后就保留在页面上,不再移除屏幕
app:layout_scrollFlags="scroll|exitUntilCollapsed" >
<ImageView
android:id="@+id/fruit_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:fitsSystemWindows="true" // 表示会为系统状态栏留出空间
// 用于指定当前控件在 CollapsingToolbarLayout 折叠过程中的折叠模式
app:layout_collapseMode="parallax" /> // parallax 表示会在折叠的过程中产生一定的错位偏移,这种模式的视觉效果会很好
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" /> // pin 表示在折叠过程中位置始终保持不变
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
// 内容详情部分
// NestedScrollView 在 ScrollView 的基础之上增加了嵌套响应滚动事件的功能
// 由于 CoordinatorLayout 本身已经可以响应滚动事件了,因此在它的内部就需要使用 NestedScrollView 或 RecyclerView 这样的布局
// 不管是 ScrollView 还是 NestedScrollView,它们的内部都只允许存在一个直接子布局,否则 AS 会有提示
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" >
<LinearLayout
android:layout_width="match_parent"
android:layout_heigh="match_parent"
android:orientation="vertical" >
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="35dp"
app:cardCornerRadius="4dp" // 圆角
app:elevation="5dp" > // 阴影
<TextView
android:id="@+id/fruit_content_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp" />
</android.support.v7.widget.CardView>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/ic_comment"
app:layout_anchor="@id/appBar" // 指定将 AppBarLayout 作为一个锚点,这样悬浮按钮就会出现在标题栏的区域内
app:layout_anchorGravity="bottom|end" /> // 将悬浮按钮定位在标题栏区域的右下角
</android.support.design.widget.CoordinatorLayout>
13. 怎样实现沉浸式状态栏
Android 5.0 系统之后支持沉浸式系统状态栏,有两种实现方式
方式一(xml 文件实现)
- 第一步:必须将
ImageView
布局结构以及所有父布局都设置fitsSystemWindows
属性为true
- 第二步:必须在应用的主题中将状态栏颜色指定成透明色。可以创建一个
values-21
目录,目录下创建一个style.xml
文件,对属性android:statusBarColor
的值指定成@android:color/transparent
即可。因为values-21
目录是只有 Android 5.0 即以上的系统才会去读取,所以这么声明式没有问题的 - 第三步,因为 Android 5.0 之前的系统无法识别新建的主题,所以对原主题文件进行修改,把
<style>
标签内去掉android:statusBarColor
属性的指定,然后对Activity
应用这个主题就可以了
- 第一步:必须将
方式二(代码实现)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class WeatherActivity extends AppCompatActivity {
...
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(Build.VERSION.SDK_INT >= 21) {
View decorView = getWindow().getDecorView();
// 表示活动的布局会显示在状态栏上面
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
// 设置状态栏为透明色
getWindow().setStatusBarColor(Color.TRANSPARENT);
}
setContentView(R.layout.activity_weather);
...
}
...
}