第二行代码学习笔记——第十二章:最佳的 UI 体验——Material Design 实战
来源:互联网 发布:linux删除tomcat日志 编辑:程序博客网 时间:2024/05/22 13:11
本章要点
Android为了统一界面风格,在2014年的 Google I/O 大会上推出了一套全新的界面设计语言——Material Design。
12.1 什么是Material Design
Material Design 是由Google的设计工程师们基于传统优秀的设计原则,给丰富的创意和科学技术所发明的一套全新的界面设计语言,包含了视觉,运动,互动效果等特性。
它的出现,使Android的UI界面首次超过了iOS。
为了解决面向开发者的问题,比如:很多我们的开发者不知道什么样的界面和效果叫Material Design。就算搞清楚了,实现起来也是比较困难的,于是 Google I/O 大会上推出了 Design Support 库,这个库将 Material Design 中最具有代表性的一些控件和效果进行了封装,使得我们开发者能够轻松的将自己的应用程序 Material 化。
接下来我们学习 Design Support 这个库,并且配合其他组件来完成一个优秀的Material Design 应用。
新建一个 MaterialTest 项目。
12.2 ToolBar
ToolBar是我们接触的第一个 Material Design 控件,对于它的另一个相关控件ActionBar。
ActionBar的设计被限定只能位于活动的顶部,从而不能实现 Material Design 的效果。因此官方更加推荐使用ToolBar。
ToolBar的强大之处在于,它不仅继承了ActionBar的所有功能,而且灵活性很高。
任何一个新建项目,默认都会显示ActionBar。它是根据项目中指定的主题来显示的,打开AndroidManifest.xml文件,如下:
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> ... </application>
使用android:theme属性指定了一个AppTheme的主题。打开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>
这里定义了一个叫AppTheme的主题,然后指定了它的parent主题是Theme.AppCompat.Light.DarkActionBar。这个DarkActionBar是一个深色的主题,项目中自带的ActionBar就是因为指定了这个主题才出现的。
接下来我们使用ToolBar来代替ActionBar,通常Theme.AppCompat.NoActionBar(表示深色主题,将界面的主题颜色设成深色,陪衬颜色设成淡色)和Theme.AppCompat.Light.NoActionBar(表示淡色主题,将界面的主题颜色设成淡色,陪衬颜色设成深色)两种主题可选。那我们就选用淡色主题,如下所示:
<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></resources>
在AppTheme中重写了colorPrimary,colorPrimaryDark和colorAccent这3个属性的颜色。
现在我们已经将ActionBar隐藏起来了,使用ToolBar来代替ActionBar。修改activity_main.xml中的代码如下:
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" 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>
使用了 xmlns:app 指定了一个新的命名空间。是由于 Material Design 是在 Android5.0 系统中才出现的,为了兼容之前老的系统,我们就必须使用app:attribute。
定义了一个Toolbar控件,它是有appcompat-v7库提供的。为了让ToolBar单独使用深色主题,这里我们使用 android:theme 属性,将Toolbar的主题指定成了ThemeOverlay.AppCompat.Dark.ActionBar。为了使Toolbar中的菜单按钮弹出的菜单项也变成淡色的主题,这里使用了 app:popupTheme ,是因为popupTheme这个属性是 Android5.0 新增的,这样我们就可以兼容 Android5.0 以下的系统了。
接下来我们修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity { private Toolbar toolbar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); //将Toolbar的实例传入 }}
这样我们就让它的外观和功能都和ActionBar一致了。运行程序如下:
Toolbar常用的一些功能,比如修改标题栏上显示的文字内容。在AndroidManifest.xml中指定,如下:
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="Fruits"> ... </activity> </application>
这里在activity新增了一个label属性,用于指定Toolbar显示的文字内容,如果没有指定就会默认使用application中指定的label内容,也就是我们的应用的名称。
为了丰富我们Toolbar,可以再添加 action 按钮。准备一些图片来作为按钮的图标。将它们放在 drawable-xxhdpi 目录下。创建一个 meum 文件夹。再创建一个toolbar.xml 文件,并编写代码如下:
<?xml version="1.0" encoding="utf-8"?><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" /> <item android:id="@+id/delete" android:icon="@drawable/ic_delete" android:title="Detele" app:showAsAction="ifRoom" /> <item android:id="@+id/settings" android:icon="@drawable/ic_settings" android:title="Settings" app:showAsAction="never" /></menu>
通过< item>标签定义 action 按钮,android:id 用于指定按钮的 id,android:icon 用于指定按钮的图标,android:title 指定按钮的文字。app:showAsAction 指定按钮显示的位置,使用app命名空间,为了能够兼容更低的系统。可选值:always(永远显示在Toolbar中,屏幕空间不足则不显示),ifRoom(屏幕空间足够的情况下显示在Toolbar中,不够则显示在菜单当中),never(永远显示在菜单中)。注意:Toolbar中的action按钮只会显示图标,菜单中的 action 只会显示文字。
修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity { ... /** * 加载toolbar.xml文件 * * @param menu * @return */ @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.toolbar, menu); return true; } /** * 处理各个按钮的点击事件 * * @param item * @return */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.backup: Toast.makeText(this, "You clicked Backup", Toast.LENGTH_SHORT).show(); break; case R.id.delete: Toast.makeText(this, "You clicked Delete", Toast.LENGTH_SHORT).show(); break; case R.id.settings: Toast.makeText(this, "You clicked Settings", Toast.LENGTH_SHORT).show(); break; default: break; } return true; }}
重新运行程序,效果如下:
这些 action 按钮都是可以响应点击事件的。
Toolbar的功能还远远不足这些,后面我们会结合其他控件来挖掘Toolbar的更多功能。
12.3 滑动菜单
滑动菜单是 Material Design 中最常见的效果之一。
12.3.1 DrawerLayout
滑动就是将菜单选项隐藏起来,通过滑动来将菜单显示出来。
Google提供了一个DrawerLayout控件,实现滑动菜单简单又方便。
DrawerLayout的用法:它是一个布局,在布局中放入两个直接子控件,第一个子控件是显示在主屏幕中的显示内容,第二个控件是滑动菜单中显示的内容。修改activity_main.xml中的代码如下:
<?xml version="1.0" encoding="utf-8"?><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> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="start" android:background="#FFF" android:text="This is Menu" android:textSize="30sp" /></android.support.v4.widget.DrawerLayout>
这个DrawerLayout是由 support-v4 库提供的。 我们可以看到第一个子控件FrameLayout(用于作为主屏幕显示的内容);第二个子控件TextView(作为滑动菜单中显示的内容),这个子控件中 android:layout_gravity 这个属性必须指定(滑动菜单是在屏幕的左边还是右边,left,right,这里我指定了start,根据系统语言进行判断,比如英语,汉语,滑动就在左边,阿拉伯语滑动就在右边)。
运行程序,左侧向右侧滑动,如图:
向左滑动,或者点击菜单以外的区域,都可以将滑动菜单关闭。
为了解决用户不知道这个功能,Material Design建议我们在Toolbar 的最左边接入了一个导航按钮,点击也会将滑动菜单展示出来。(这样就相当于给用户提供了良两种打开滑动菜单的方式)。
将准备好的c_menu.png放在drawable-xxhdpi目录下,修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity { private Toolbar toolbar; private DrawerLayout mDrawerLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mDrawerLayout= (DrawerLayout) findViewById(R.id.drawer_layout); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); //显示导航按钮 actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);//设置导航按钮图标(默认返回箭头,含义返回上一个活动) } } ... @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: mDrawerLayout.openDrawer(GravityCompat.START);//对HomeAsUp按钮(id永远是 android.R.id.home),显示滑动菜单,传入GravityCompat.START break; ... } return true; }}
运行程序,如下:
在Toolbar 最左边就会出现导航按钮,点击按钮,滑动菜单就会显示。
12.3.2 NavigationView
优化滑动菜单界面,Google官方给我们提供了NavigationView,它是 Design Support 库中的一个控件,它不仅是严格按照 Material Design 的要求设计的,而且可以将滑动变的非常简单。
导入依赖包:
compile 'com.android.support:design:24.2.1' compile 'de.hdodenhof:circleimageview:2.1.0'
第一个依赖库就是 Design Support 库;第二个库是一个开源的CircleImageView,轻松实现圆形化的功能。
我们首先要准备:menu(在NavigationView中显示的菜单项) 和 headerLayout(在NavigationView中显示头部布局的)。
准备几张图片放在了drawable-xxhdpi目录下。右击menu,创建一个nav_menu.xml文件,编写代码如下:
<?xml version="1.0" encoding="utf-8"?><menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:checkableBehavior="single"> <item android:id="@+id/nav_call" android:icon="@drawable/nav_call" android:title="Call" /> <item android:id="@+id/nav_friends" android:icon="@drawable/nav_friends" android:title="Friends" /> <item android:id="@+id/nav_loation" android:icon="@drawable/nav_location" android:title="Location" /> <item android:id="@+id/nav_mail" android:icon="@drawable/nav_mail" android:title="Mail" /> <item android:id="@+id/nav_task" android:icon="@drawable/nav_task" android:title="Task" /> </group></menu>
在< menu>嵌套了< group>(一组),将group的checkableBehavior属性设为single(所有菜单项只能单选)。接下来定义了5个菜单项。这样准备好了menu。
headerLayout, 这是一个随意定制的布局,那我们就在里面放置头像,用户名,邮箱吧。
准备一张图片放在drawable-xxhdpi目录下。最好是一张正方形,因为一会将它圆形化。右击layout,创建一个nav_header.xml文件。修改代码如下:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="180dp" android:background="?attr/colorPrimary" android:padding="10dp"> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/icon_image" android:layout_width="70dp" android:layout_height="70dp" android:layout_centerInParent="true" android:src="@drawable/nav_icon" /> <TextView android:id="@+id/tv_mail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:text="Jack@mail.com" android:textColor="#FFF" android:textSize="14sp" /> <TextView android:id="@+id/tv_username" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/tv_mail" android:text="Jack" android:textColor="#FFF" android:textSize="14sp" /></RelativeLayout>
接下来我们使用 NavigationView 了,修改activity_main中的代码如下:
<?xml version="1.0" encoding="utf-8"?><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" android:layout_gravity="start" app:headerLayout="@layout/nav_header" app:menu="@menu/nav_menu" /></android.support.v4.widget.DrawerLayout>
这样NavigationView定义好了。
接下来处理菜单的点击事件,修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity { private Toolbar toolbar; private DrawerLayout mDrawerLayout; private NavigationView navView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); navView= (NavigationView) findViewById(R.id.nav_view); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeAsUpIndicator(R.drawable.ic_menu); } navView.setCheckedItem(R.id.nav_call); //设置默认选中 navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { //菜单选项监听事件 @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { //处理逻辑 mDrawerLayout.closeDrawers(); return true; } }); } ... }
运行程序,点击Toolbar左侧导航按钮,如下:
Material Design 它是一种非常美观的设计理念。
12.4 悬浮按钮和可交互提示
立体效果的代表性的设计:悬浮按钮。
一种可交互式提示工具(可对用户做出响应)。
12.4.1 FloatingActionButton
FloatingActionButton也是Design Support 库中的一个控件,来实现悬浮按钮的效果。还可以给这个按钮指定图标,表示来做什么。
我们准备好一个ic_done.png,修改activity_main.xml中的代码如下:
<?xml version="1.0" encoding="utf-8"?><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" /> <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" android:src="@drawable/ic_done" app:elevation="8dp" /> </FrameLayout> ...</android.support.v4.widget.DrawerLayout>
end和之前的start原理是一样的。app:elevation属性给FloatingActionButton按钮设置阴影(指定高度值)。其实默认的FloatingActionButton效果足够了。
处理FloatingActionButton点击事件,
运行程序,如下:
public class MainActivity extends AppCompatActivity { private Toolbar toolbar; private DrawerLayout mDrawerLayout; private NavigationView navView; private FloatingActionButton fab; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "FAB clicked", Toast.LENGTH_SHORT).show(); } }); } ... }
运行程序,点击FloatingActionButton按钮,如下:
12.4.2 Snackbar
Design Support 库提供的更加先进的提示工具——Snackbar。
Toast作用是告诉用户发生了什么事情,但同时用户只能被动接收这个事情,没有办法让用户原则。
Snackbar提示加入一个可交互按钮,当用户点击可以执行一些额外的操作。
Snackbar的用法:可以增加额外的点击事件。修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity { private Toolbar toolbar; private DrawerLayout mDrawerLayout; private NavigationView navView; private FloatingActionButton fab; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Snackbar.make(v,"Data deleted",Snackbar.LENGTH_SHORT).setAction("Undo", new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH_SHORT).show(); } }).show(); } }); } ... }
调用Snakebar的make()来创建一个Snakebar对象,第一个参数传入view(当前布局的任意一个View,自动查找最外层布局),用户展示Snakebar。第二个参数Snakebar显示的内容,第三个参数用于显示的时长。
紧接着调用setAction()设置执行一个动作。从而可以和用户进行交互。最后调用show()显示。
重新运行程序,点击悬浮按钮,如下:
Snakebar上面有我们提示的文字,UNDO按钮可以点击。过一段时间Snakebar从底部消失(自带动画,用户提体比较好)。
这个Snakebar将我们的悬浮按钮遮挡了。这是一个bug,影响用户体验。解决办法借助CoordinatorLaoyout轻松解决。
12.4.3 CoordinatorLaoyout
CoordinatorLaoyout是一个加强版的FrameLayout。它也是Design Support 库提供的。它可以监听所有子控件的各种事情,自动帮助我们做出合理的响应。
只需要CoordinatorLaoyout来替换我们的FrameLayout,修改main_activity.xml中的代码如下:
<?xml version="1.0" encoding="utf-8"?><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"> <android.support.design.widget.CoordinatorLayout 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" /> <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" android:src="@drawable/ic_done" app:elevation="8dp"/> </android.support.design.widget.CoordinatorLayout> ...</android.support.v4.widget.DrawerLayout>
运行程序,如下:
消失的时候,悬浮按钮会回到原来的位置,悬浮按钮的上下偏移也伴随着动画的效果,且与Snakebar完全同步,整体效果赏心悦目。
Snakebar传入的View是当前的FloatingActionButton,所以就能通过CoordinatorLayout监听到了。
12.5 卡片式布局
水果图片Material化,实现卡片式布局的效果。并且能够拥有圆角和投影。
12.5.1 CardView
CardView是实现卡片式布局的重要控件(appcompat-v7),它也是一个FrameLayout,只是额外增加了圆角和投影的效果,立体感。
CardView 的基本用法,代码如下:
<android.support.v7.widget.CardView android:layout_width="match_parent" app:cardCornerRadius="4dp" android:elevation="5dp" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_info" android:layout_width="match_parent" android:layout_height="wrap_content" /> </android.support.v7.widget.CardView>
定义CardView布局,通过app:cardCornerRadius属性指定卡片圆角的弧度;app:elevation属性指定卡片的高度。与FloatingActionButton一致。
将TextView放置在CardView中,这样TextView就会显示在卡片中了。
使用RecycleView来填充这个项目的主界面部分。升级水果图片列表。先将准备好的图片放置在项目中。
添加依赖:
compile 'com.android.support:cardview-v7:24.2.1'compile 'com.android.support:recyclerview-v7:24.2.1'compile 'com.github.bumptech.glide:glide:4.0.0-RC0'
最后我们添加了Glide库的依赖,它是一个超级强大的图片加载库,不仅可以加载本地图片,也可以加载网络图片,GIF图片,甚至是本地视频。
Glide的项目主页地址:https://github.com/bumptech/glide。
修改MainActivity中的代码如下:
<?xml version="1.0" encoding="utf-8"?><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"> <android.support.design.widget.CoordinatorLayout 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" /> <android.support.v7.widget.RecyclerView android:id="@+id/recycle_view" android:layout_width="match_parent" android:layout_height="match_parent" /> <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" android:src="@drawable/ic_done" app:elevation="8dp" /> </android.support.design.widget.CoordinatorLayout> ...</android.support.v4.widget.DrawerLayout>
接下来,定义Fruit实体类:
public class Fruit { private String name; private int imageId; public Fruit(String name, int imageId) { this.name = name; this.imageId = imageId; } public String getName() { return name; } public int getImageId() { return imageId; }}
接下来定义RecycleView的子项布局fruit_item.xml文件,代码如下:
<?xml version="1.0" encoding="utf-8"?><android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" app:cardCornerRadius="4dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <ImageView android:id="@+id/fruit_image" android:layout_width="match_parent" android:layout_height="100dp" android:scaleType="centerCrop" /> <TextView android:id="@+id/fruit_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="5dp" android:textSize="16sp" /> </LinearLayout></android.support.v7.widget.CardView>
Image中使用了scaleType属性,指定图片的缩放式。centerCrop(原有图片充满ImageView)。
接下来为RecycleView准备FruitAdapter适配器,继承自RecycleView.Adapter,并将泛型指定为FruitAdapter.ViewHolder,代码如下:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> { private Context mContext; private List<Fruit> mFruitList; public FruitAdapter(Context mContext, List<Fruit> mFruitList) { this.mContext = mContext; this.mFruitList = mFruitList; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (mContext == null) { mContext = parent.getContext(); } View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { Fruit fruit = mFruitList.get(position); holder.fruit_name.setText(fruit.getName()); Glide.with(mContext).load(fruit.getImageId()).into(holder.fruit_image); } @Override public int getItemCount() { return mFruitList.size(); } static class ViewHolder extends RecyclerView.ViewHolder { CardView cardView; ImageView fruit_image; TextView fruit_name; public ViewHolder(View itemView) { super(itemView); cardView = (CardView) itemView; fruit_image = (ImageView) itemView.findViewById(R.id.fruit_image); fruit_name = (TextView) itemView.findViewById(R.id.fruit_name); } }}
Glide的用法:首先调用Glide的with()方法并传入一个Context,Activity或Fragment参数,然后调用load()方法,传入图片的URL地址,也可以是本地路径,或者是id,接着调用info()方法设置到具体的哪个ImageView。
最后修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity { private List<Fruit> mData = new ArrayList<>(); private Fruit[] fruits = {new Fruit("Apple", R.drawable.apple), new Fruit("Banana", R.drawable.banana), new Fruit("Orange", R.drawable.orange), new Fruit("Watermelon", R.drawable.watermelon), new Fruit("Pear", R.drawable.pear), new Fruit("Grape", R.drawable.grape), new Fruit("Pineapple", R.drawable.pineapple), new Fruit("Strawberry", R.drawable.strawberry), new Fruit("Cherry", R.drawable.cherry), new Fruit("Mango", R.drawable.mango)}; private Toolbar toolbar; private DrawerLayout mDrawerLayout; private NavigationView navView; private FloatingActionButton fab; private RecyclerView mRecyclerView; private FruitAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... initFruits(); mRecyclerView = (RecyclerView) findViewById(R.id.recycle_view); GridLayoutManager manager = new GridLayoutManager(this, 2); mRecyclerView.setLayoutManager(manager); adapter = new FruitAdapter(this, mData); mRecyclerView.setAdapter(adapter); } private void initFruits() { mData.clear(); for (int i = 0; i < 50; i++) { Random random = new Random(); int index = random.nextInt(fruits.length); mData.add(fruits[index]); } } ... }
运行程序如下:
我们把精美的图片展示在单独的卡片当中,并且还有圆角和阴影(美观)。
我么发现了一个bug,Toolbar被RecycleView遮住了。为了解决这个问题,借助工具——AppBarLayout。
12.5.2 AppBarLayout
既然我们使用的是CoordinatorLayout(原理),自然会有更加巧妙的办法解决。
Design Support中的AppBarLayout。实际是一个垂直方向的LinerLayout,它在内部做了很多滚动封装,使用Material Design设计理念。
只需两步解决Toolbar覆盖问题:第一步,将Toolbar嵌套到AppBarLayout中,第二步,给RecycleView指定一个布局(app:layout_behavior)。修改activity_main.xml中的代码如下:
<?xml version="1.0" encoding="utf-8"?><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"> <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.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" /> </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView android:id="@+id/recycle_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> ...</android.support.v4.widget.DrawerLayout>
appbar_scrolling_view_behavior是Design Support提供的。
运行程序如下,你会发现一切正常:
进一步优化,AppBarLayout实现 Material Design 效果,当AppBarLayout接收到滚动事件,通过 app:layout_scrollFlags 属性实现内部子控件的事件。修改activity_main中的代码如下:
<?xml version="1.0" encoding="utf-8"?><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"> <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.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:layout_scrollFlags="scroll|enterAlways|snap" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> </android.support.design.widget.AppBarLayout> ...</android.support.v4.widget.DrawerLayout>
指定app:layout_scrollFlags属性scroll|enterAlways|snap。scroll表示当RecycleView向上滚动的时候,Toolbar会跟着向上滑动并实现隐藏;enterAlways表示当RecycleView向下滑动并重新显示。snp表示Toolbar还没有完全隐藏或显示的时候,根据当前滚动的距离自动选择显示还是隐藏。
运行程序,向上滑动,如图:
这是 Material Design 的一种设计思想。增加用户的体验度。
12.6 下拉刷新
Material Design 中制定了Android的统一的下拉刷新的风格。
SwipeRefreshLayout(support-v4)是用于实现下拉刷新的核心类。
在RecycleView外层嵌套SwipeRefreshLayout,修改activity_main.xml中的代码,如下:
<?xml version="1.0" encoding="utf-8"?><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"> <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> ... <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipe_refresh" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <android.support.v7.widget.RecyclerView android:id="@+id/recycle_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.v4.widget.SwipeRefreshLayout> ...</android.support.v4.widget.DrawerLayout>
修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity { ... private SwipeRefreshLayout swipeRefresh; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh); swipeRefresh.setColorSchemeResources(R.color.colorPrimary); //设置颜色 swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public 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(); } ... }
重新运行程序,在屏幕往下托,就会出现下拉刷新的进度条,松手就会自动刷新,如图:
标准的Material Design 下拉刷新效果。
12.7 可叠式标题栏
实现可叠式标题栏——CollapsingToolbarLayout。
12.7.1 CollapsingToolbarLayout
CollapsingToolbarLayout(Design Support)它是一个作用于Toolbar基础之上的布局。它可以让Toolbar的效果变得更加丰富,实现非常华丽的效果。
CollapsingToolbarLayout是不能独立存在的,它只限制在作为AppBarLayout子布局来使用。而AppBarLayout又必须作为CoordinatorLayout的子布局。
创建水果的详情展示界面,创建FruitActivity,并指定布局成为activity_fruit.xml,主要分为两部分:一个是水果标题栏,一个是水果内容详情。
首先实现标题栏部分,这里使用CoordinatorLayout来作为最外层布局,里面嵌套AppBarLayout,在AppBarLayout里面嵌套CollapsingToolbarLayout布局,代码如下:
<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout 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: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"> <ImageView android:id="@+id/fruit_image_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" app:layout_collapseMode="parallax" /> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout></android.support.design.widget.CoordinatorLayout>
实现更加高级的Toolbar效果,我们把android:theme主题指定在上一层。app:contentScrim属性用于指定折叠状态以及折叠后的背景色。(折叠后就是一个普通的Toolbar)。 app:layout_scrollFlags属性中的sroll(CollapsingToolbarLayout会随着内容详情页一起滚动),exitUntilCollapsed(CollapsingToolbarLayout滚动完折叠之后就保留在界面上,不会移除屏幕)。
里面我们定义了一个ImaggView和Toolbar的高级标题(普通标题栏加上图片合成)。app:layout_collapseMode属性(用于当前控件在CollapsingToolbarLayout折叠过程中的折叠模式,pin表示在折叠过程中位置保持始终不变,parallax表示折叠过程中产生一定的错位偏移,视觉效果非常棒)。
接下来编写水果内容详情部分,继续修改activity_fruit.xml中的代码:
<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout 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: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.AppBarLayout> <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_height="wrap_content" 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"> <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" app:layout_anchorGravity="bottom|end" /></android.support.design.widget.CoordinatorLayout>
NestedScrollView布局和AppBarLayout是同级,与SrollView用法一样,增加了嵌套响应滚蛋事件的功能。只允许存放在一个直接子布局。app:layout_behavior属性指定行为布局,与之前的RecycleView用法一样。
之后我们加入悬浮按钮了,获得额外的动画效果。FloatingActionButton中的app:layout_anchor(指定瞄点,设置为AppBarLayout,悬浮按钮出现在水果标题局域栏内),接着使用app:layout_anchorGravity指定它的显示位置。
编辑完成界面之后,我们修改FruitActivity中的代码如下:
public class FruitActivity extends AppCompatActivity { public static final String FRUIT_NAME = "fruit_name"; public static final String FRUIT_IMAGE_ID = "fruit_image_id"; private Toolbar toolbar; private ImageView fruitImageView; private TextView fruitContentText; private CollapsingToolbarLayout collapsingToolbar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fruit); Intent intent = getIntent(); String fruitName = intent.getStringExtra(FRUIT_NAME); String fruitNameId = intent.getStringExtra(FRUIT_IMAGE_ID); toolbar = (Toolbar) findViewById(R.id.toolbar); fruitImageView = (ImageView) findViewById(R.id.fruit_image_view); fruitContentText = (TextView) findViewById(R.id.fruit_content_text); collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); //默认按钮箭头 } collapsingToolbar.setTitle(fruitName); //设置当前标题 Glide.with(this).load(fruitNameId).into(fruitImageView); String fruitContent = generateFruitContent(fruitName); fruitContentText.setText(fruitContent); } /** * 水果名循环拼接500次 * * @param fruitName * @return */ private String generateFruitContent(String fruitName) { StringBuilder fruitContent = new StringBuilder(); for (int i = 0; i < 500; i++) { fruitContent.append(fruitName); } return fruitContent.toString(); } /** * 处理HomeAsUp按钮的点击事件 * * @param item * @return */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); //返回上一个Activity break; } return super.onOptionsItemSelected(item); }}
接下来我们修改FruitAdapter来处理RecycleView的点击事件:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> { ... @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (mContext == null) { mContext = parent.getContext(); } View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item, parent, false); final ViewHolder holder = new ViewHolder(view); holder.cardView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int position = holder.getAdapterPosition(); Fruit fruit = mFruitList.get(position); Intent intent = new Intent(mContext, FruitActivity.class); intent.putExtra(FruitActivity.FRUIT_NAME, fruit.getName()); intent.putExtra(FruitActivity.FRUIT_IMAGE_ID, fruit.getImageId()); mContext.startActivity(intent); } }); return holder; }...}
我们给CardView注册了一个点击事件监听器。来启动FruitActivity。
重新运行程序,并点击任意水果,效果如下:
当我们向上滑动的时候,水果的背景图片的标题会慢慢的缩小,产生一些偏移的效果,如下:
继续向上拖动,直到标题栏完全处于折叠状态,如下:
这个时候我们向下拖动,就会执行一个相反的过程。
12.7.2 充分利用系统状态栏空间
Android5.0系统之后支持对状态栏和背景进行操作(使背景图和状态栏融合)。
我们需要借助android:fitsSystemWindows这个属性来完成。在CoordinatorLayout,AppBarLayout,CollapingToobarLayout这种嵌套结构的布局中还,将这个属性设置为true(表示该控件会出现在系统状态栏里)。
修改activiy_frui.xml中的代码如下:
<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" 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" android:fitsSystemWindows="true"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:id="@+id/fruit_image_view" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:scaleType="centerCrop" app:layout_collapseMode="parallax" /> ... </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> ...</android.support.design.widget.CoordinatorLayout>
设置完之后,我们还必须将程序中的状态栏指定为透明色。由于android:statusBarColor(@android:color/transparent)属性是Android5.0系统开始有的,系统差异的实现:
右击res—>New—>Directory,创建一个 values-21目录,在里面新建styles.xml文件进行编写:
<?xml version="1.0" encoding="utf-8"?><resources> <style name="FruitActivityTheme" parent="AppTheme"> <item name="android:statusBarColor">@android:color/transparent</item> </style></resources>
这里定义了一个FruitActivityTheme主题,它的父主题是AppTheme(继承了AppTheme所有的特性)。然后我们在这个主题里将状态栏指定为透明色。(Android5.0以上系统)。
为了能够让Android5.0系统识别定义FruitActivityTheme主题,我们修改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="FruitActivityTheme" parent="AppTheme"></style></resources>
定义FruitActivityTheme主题,由于Android5.0之前无法指定颜色,所以里面不进行操作。
最后我们需要让FruitActivity使用这个主题,修改AndroidManifest.xml中的代码如下:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.hjw.materialtest"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> ... <activity android:name=".FruitActivity" android:theme="@style/FruitActivityTheme"></activity> </application></manifest>
运行在Android5.0以及以上的系统,水果详情页界面的效果如下:
12.8 小结与点评
我们充分的使用的Design Support库,support-v4库,support-v7库以及开源库实现了宇哥Material化的程序。
了解Material Design 的设计思维和设计理念,请参见:https://material.google.com。
- 第二行代码学习笔记——第十二章:最佳的 UI 体验——Material Design 实战
- Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验
- 第一行代码第二版(郭霖著)笔记之第十二章(Material Design 实战)
- Material Design 学习之Toolbar(第二行代码笔记)
- Android学习笔记——Material Design
- Material Design —— TabLayout学习笔记
- 第一行代码读书笔记-第十二章 Material Design实战
- Material Design UI Widgets ——FloatingActionButton
- Material Design UI Widgets —— StatusBar
- 使用Material Design,实现UI的体验的最佳快捷实现!
- Android设计——Material design学习笔记
- Material Design 开发笔记 —— ButtomNavigationView
- Material Design 学习笔记 -- Layout --> Responsive UI
- 第二行代码学习笔记——第三章:软件也要拼脸蛋——UI开发的点点滴滴
- Android学习笔记之Material Design实战
- ANDROID L——Material Design详解(UI控件)
- ANDROID L——Material Design详解(UI控件)
- ANDROID L——Material Design详解(UI控件)
- postfix邮件服务:远程发送及访问控制
- php中对象是引用类型吗?
- 猜数游戏
- ConcurrentHashMap
- MySql建数据库,建表,建表约束
- 第二行代码学习笔记——第十二章:最佳的 UI 体验——Material Design 实战
- python定期爬取GitHub上每日流行项目
- t-SNE理解
- 算法笔记
- Android集成支付宝支付(最新版,无脑操作)
- ORA-01403: 未找到任何数 ORA-06512
- StackOverflow内存溢出
- 关于数组、结构体的初始化
- flex模式文字溢出问题