Android学习之基础知识十五 — 最佳UI体验(Material Design实战)
一、前言
长久以来,大多数人都认为Android系统的UI并不美观,至少没有iOS系统的美观。以至于很多IT公司在进行应用界面设计的时候,为了保证双平台的统一性,强制要求Android端的界面风格必须和iOS端一致,这种情况在现实工作中实在是太常见了,因为对于一般用户来说,他们不太可能会在两个操作系统上分别去使用同一个应用,但是却必定会在同一个操作系统上使用不同的应用。因此同一个操作系统中各个应用之间的界面统一性要远比一个应用在双平台的界面统一性重要的多,只有这样,才能给使用者带来更好的用户体验。
但是问题在于,Android标准的界面设计风格并不是特别被大众所接受,很多公司都觉得自己完全可以设计出更好看的界面,从而导致Android平台的界面风格长期难以得到统一。为了解决这个问题,谷歌在2014年Google I/O大会上重磅推出了一套全新的界面设计语言——Material Design。
二、什么是Material Design
Material Design是由谷歌的设计工程师们基于传统优秀的设计原则,结合丰富的创意和科学技术所发明的一套全新的界面设计语言,包含了视觉、运动、互动效果等特性。那么谷歌凭什么认为Material Design就能解决Android平台界面风格不统一的问题呢?一言以蔽之,好看。
这次谷歌在界面设计上确实下了大功夫,很对媒体评论,Material Design的出现使得Android首次在UI方面超越了iOS,按照正常的思维来想,如果各个公司都无法设计出比Material Design更加出色的界面风格,那么它们就应该理所应当的使用Material Design来设计界面,从而解决了Android平台界面风格不统一的问题了。
为了做出表率,谷歌从Android5.0系统开始,就将所有内置的应用都使用Material Design风格来进行设计。不过,在重磅推出之后,Material Design的普及程度却不能说是特别理想,因为这只是一个推荐的设计规范,主要是面向UI设计人员的,而不是面向开发者的。很多开发者可能根本就搞不清楚什么样的界面和效果才叫Material Design,就算搞清楚了,实现起来也会很费劲,因为不少Material Design的效果是很难实现的,而Android中却几乎没有提供相应的API支持,一切都要靠开发者自己从零写起。
谷歌当然也意识到了这个问题,于是在2015年的Google I/O大会上推出了一个Design Support库,这个库将Material Design中最具代表性的一些控件和效果进行了封装,使得开发者在即使不了解Material Design的情况下也能非常轻松的将自己的应用Material化。下面我们就针对Design Support这个库进行深入的学习,并且配合一些其他的控件来完成一个优秀的Material Design应用。
先新建一个MaterialTest项目:
三、Toolbar
Toolbar将会是我们接触的第一个Material控件。虽说对于Toolbar暂时还是比较陌生,但是对于它的另一个相关控件ActionBar,就应该有点熟悉了。前面有一节为了使用一个自定义的标题栏,而把系统原生的ActionBar隐藏掉,每个活动最顶部的那个标题栏其实就是ActionBar,之前我们编写的所有程序里一直都有ActionBar的身影。
不过ActionBar由于其设计的原因,被限定只能位于活动的顶部,从而不能实现一些Material Design的效果,因此官方现在已经不再建议使用ActionBar了,而是更加推荐Toolbar。
Toolbar的强大之处在于,它不仅继承了ActionBar的所有功能,而且灵活性很高,可以配合其他控件来完成一些Material Design的效果,下面我们就来具体学习一下:
任何一个新建的项目,默认都是会显示ActionBar的,那么ActionBar到底是从哪里来的呢?其实这是根据项目中指定的主题来显示的,打开AndroidManifest文件查看:
可以看到,这里使用android:theme属性指定了一个AppTheme的主题,那么这个AppTheme又是在哪里定义的呢?打开:res/values/styles.xml文件,代码如下所示:
这里定义了一个叫AppTheme的主题,然后指定它的parent主题是:Theme.AppCompat.Light.DarkActionBar。这个DarkActionBar是一个深色的ActionBar主题,我们之前所有的项目中自带的ActionBar就是因为指定了这个主题才出现的。
而现在我们准备使用Toolbar来提到ActionBar,因此需要指定一个不带ActionBar的主题,通常有:Theme.AppCompat.NoActionBar和Theme.AppCompat.Light.NoActionBar这两种主题可选。其中Them.AppCompat.NoActionBar表示深色主题,它会将界面的主题颜色设成深色,陪衬颜色设成淡色。而Theme.AppCompat.Light.NoActionBar表示淡色主题,它会将界面的主题颜色设成淡色,陪衬颜色设成深色。具体的效果你可以动手试试,这里由于我们之前的程序一直都死以淡色为主,那就选用淡色主题了,如下所示:
然后观察一下AppTheme中的属性重写,这里重写了colorPrimary、colorPrimaryDark、colorAccent这3个属性的颜色,那么这3个属性分别代表着位置什么颜色呢?可以在Android Studio左边看到颜色,位置如图:
除了上述3个属性之外,我们还可以通过textColorPrimary、windowsBackground和navigationColor等属性来控制更多位置的颜色,不过唯独colorAccent这个属性比较难理解,它不只是用来指定这样一个按钮的颜色,而且更多表达了一个强调的意思,比如一些控件的选中状态也会使用colorAccent的颜色。
现在我们已经将ActionBar隐藏起来了,接下来使用Toolbar来替代ActionBar:
第一步:修改activity_main.xml中的代码:
虽然这段代码不长,但是里面着实有不少技术点是需要我们去仔细琢磨的。首先看一下第三行,这里使用了xmlns:app指定了一个新的命名空间,思考一下,正是由于每个布局文件都会使用xmlns:android来指定一个命名空间,因此才能一直使用android:id、android:layout_width等写法,那么这里指定了xmlns:app,也就是说现在可以使用app:attribute这样的写法了,但是为什么这里要指定一个xmlns:app的命名空间呢?这是由于Material Design是在Android5.0系统中才出现的,而很多的Material属性在5.0之前的系统中并不存在,那么为了能够兼容之前的老系统,我们就不能使用android:attribute这样的写法了,而是应该使用app:attribute。
接下来定义了一个Toolbar控件,这个控件是由appcompat-v7库提供的。这里我们给Toolbar指定了一个id,将它的宽度设置为match_parent,高度设置为actionBar的高度,背景颜色设置为colorPrimary。不过下面的部分就稍微有点难理解了,由于我们刚才在styles.xml中将程序的主题指定成了淡色主题,因此Toolbar现在也是淡色主题,而Toolbar上面的各种元素就会自动使用深色系,这是为了和主体颜色区别开。但是这个效果看起来就会很差,之前使用ActionBar时文字都是白色的,现在变成黑色的会很难看。那么为了能让Toolbar单独使用深色主题,这里我们使用android:theme属性,将Toolbar的主题指定成了ThemeOverlay.AppCompat.Dark.ActionBar。但是这样指定完了之后又会出现新的问题,如果Toolbar中有菜单按钮,那么弹出的菜单也会变成深色主题,这样就再次变得十分难看,于是这里使用了app:popupTheme属性单独将弹出的菜单项指定为淡色主题,之所以使用app:popupTheme,是因为popupTheme这个属性是在Android5.0系统中新增的,我们使用app:popupTheme的话就可以兼容Android5.0以下的系统了。
如果你觉得上面的描述很绕的话,可以自己动手做一做实验,看看不指定上述主题会是什么样的效果,这样你会理解得更加深刻。
第二步:修改MainActivity中的代码,注意:这里Toolbar有两个引用包,选择:android.support.v7.widget.Toolbar包
这里关键的代码只有两句,首先通过findViewById()得到Toolbar的实例,然后调用setSupportActionBar()方法并将Toolbar的实例传入,这样我们就做到既使用了Toolbar,又让它的外观与功能都和ActionBar一致了。
第三步:运行程序,效果如下:
这个标题栏我们再熟悉不过了,虽然看上去和之前的标题没什么两样,但其实它已经是Toolbar而不是ActionBar了,因此它现在也具备了实现Material Design效果的能力,这个我们在后面就会学到。
接下来我们再学习一些Toolbar比较常用的功能吧,比如修改标题栏上显示的文字内容,这段文字内容是在AndroidManifest.xml中指定的,如下所示:
这里给activity增加了一个android:label属性,用于指定在Toolbar中显示的文字内容,如果没有指定的话,会默认使用application中指定的label内容,也就是我们的应用名称。
不过只有一个标题的Toolbar看起来太单调了,我们还可以再添加一些action按钮来让Toolbar更加丰富一些,这里我提前准备了几张图片来作为按钮的图标,将它们放置在了mipmap-xxhdpi目录下,现在右击res目录 —> New —> Directory,创建一个menu文件夹。然后右击menu文件夹 —> New —> Menu resource file,创建一个toolbar.xml文件,并编写如下代码:
可以看到,我们通过<item>标签来定义action按钮,android:id用于指定按钮的id,android:icon用于指定按钮的图标。android:title用于指定按钮的文字。
接着使用app:showAsAction来指定按钮的显示位置,之所以这里再次使用了app命名空间,同样是为了能够兼容低版本的系统。showAsAction主要有以下几种值可选:always表示永远显示在Toolbar中,如果屏幕空间不够则不显示;ifRoom表示屏幕空间足够的情况下显示在Toolbar中,不够的话就显示在菜单当中;never表示永远显示在菜单当中。注意,Toolbar中的action按钮只会显示图标,菜单中的action按钮只会显示文字。
接下来修改MainAction中的代码:
非常简单,我们在onCreateOptionMenu()方法中加载了toolbar.xml这个菜单文件,然后在onOptionsItemSelected()方法中处理各个按钮的点击事件。重新运行程序,效果如下:
可以看到,Toolbar上面现在显示了两个action按钮,这是因为Backup按钮指定的显示位置是always,Delete按钮指定的显示位置是ifRoom,而现在屏幕空间很充足,因此这两个按钮都会显示在Toolbar中,另外一个Settings按钮由于指定的显示位置是never,所以不会显示在Toolbar中,点击一下最右边的菜单按钮来展开菜单项,你就能找到Settings按钮了。另外这些action按钮都是可以响应点击事件的。
四、滑动菜单
滑动菜单可以说是Material Design中最常见的效果之一了,在许多著名的应用(如Gmail、Google+等)中,都是滑动菜单的功能。虽说这个功能看上去好像挺复杂的,不过借助谷歌提供的各种工具,我们可以很轻松的实现非常炫酷的滑动菜单效果。
4.1、DrawerLayout
所谓的滑动菜单就是将一些菜单项隐藏起来,而不是放置在主屏幕上,然后可以通过滑动的方式将菜单显示出来。这种方式既节省了屏幕空间,又实现了非常好的动画效果,是Material Design中推荐的做法。
不过如果我们全靠自己去实现上述功能的话,难度很大。幸运的是,谷歌提供了一个DrawerLayout控件,借助这个控件,实现滑动菜单简单又方便。
先简单介绍一下DrawerLayout的用法,首先它是一个布局,在布局中允许放入两个直接子控件,第一个子控件是主屏幕中显示的内容,第二个子控件是滑动菜单中显示的内容,因此,我们就可以对activity_main.xml中的代码做如下修改:
可以看到,这里最外层的控件使用了DrawerLayout,这个控件是由support-v4库提供的。DrawerLayout中放置了两个直接子控件,第一个子控件是FrameLayout,用于作为主屏幕中显示的内容,当然里面还有我们刚刚定义的Toolbar,第二个子控件这里使用了一个TextView,用于作为滑动菜单中显示的内容,其实使用什么都可以,DrawerLayout并没有限制只能使用固定的控件。
但是关于第二个子控件有一点需要注意,layout_gravity这个属性是必须指定的,因为我们需要告诉DrawerLayout滑动菜单是在屏幕的左边还是右边,指定left表示滑动菜单在左边,指定right表示滑动菜单在右边,这里指定的是start,表示会根据系统语言进行判断,如果系统语言是从左往右的, 比如英语、汉语,滑动菜单就在左边,如果系统语言从右往左的,比如阿拉伯语,滑动菜单就在右边。
现在运行一下程序,,然后在屏幕的左侧边缘向右拖动,就可以让滑动菜单显示出来了:
然后向左滑动菜单,或者点击一下菜单以外的区域,都可以让滑动菜单关闭,从而回到主界面。无论是展示还是隐藏滑动菜单,都是非常流畅的动画过渡的。
可以看到,我们只是稍微改动了一下布局文件,就能实现如此炫酷的效果,不过现在的滑动菜单还是有点问题,因为只有在屏幕的左侧边缘进行拖动时才能将菜单拖出来,而很多用户根本不知道有这个功能,那么这么提示他们呢?
Material Design建议的做法就是在Toolbar的最左边加入一个导航按钮,点击按钮也会将滑动菜单的内容展示出来,这样就相当于给用户提供了两种打开滑动菜单的方式,防止一些用户不知道屏幕的左侧边缘是可以拖动的。下面我们开始来实现这个功能:
第一步:首先要准备一张导航按钮的图标:ic_menu.png,将它放在mipmap-xxhdpi目录下。
第二步:修改MainActivity中的代码:
这里我们并没有改动多少代码,首先调用了findViewById()方法得到了DrawerLayout的实例,然后调用getSupportActionBar()方法得到了ActionBar的实例,虽然这个ActionBar的具体实现是由Toolbar来完成的,接着调用ActionBar的setDisplayHomeAsUpEnabled()方法让导航按钮显示出来,又调用了setHomeAsUpIndicator()方法来设置一个导航按钮图标,实际上,Toolbar最左侧的这个按钮就叫作HomeAsUp按钮,它默认的图标是一个返回的箭头,含义是返回上一个活动。很明显,在这里我们将它默认的样式和作用都进行了修改。
接下来在onOptionsItemSelected()方法中对HomeAsUp按钮的点击事件进行处理,HomeAsUp按钮的id永远都是:android.R.id.home。然后调用DrawerLayout的openDrawer()方法将滑动菜单展示出来,注意openDrawer()方法要求传入一个Gravity参数,为了保证这里的行为和XML中定义的一致,我们传入了GravityCompat.START。
第三步:现在重新运行程序,效果如下所示:点击小球,滑动菜单就会再次展示出来了。
4.2、NavigationView
目前我们已经成功实现了滑动菜单功能,其中滑动功能已经做得非常好了,但是菜单却还是很丑,毕竟菜单页面仅仅使用了一个TextView,非常单调。事实上,你可以在滑动菜单页面定制任意的布局,不过谷歌给我们提供了一种更好的方法——NavigationView。NavigationView是Design Support库中提供的一个控件,它不仅是严格按照Material Design的要求来进行设计的,而且还可以将滑动菜单页面的实现变得非常简单。接下来就来学习一下NavigationView的用法。
第一步:既然这个控件是Design Support库中提供的,那么就需要将这个库引入到项目中才行。打开app/build.gradle文件,在dependencies闭包中添加如下内容:
这里添加了两行依赖关系,第一行就是Design Support库,第二行是一个开源项目CircleImageView,它可以用来轻松实现图片圆形化的功能,我们待会就会用到它。CircleImageView的项目主页地址是:https://github.com/hdodenhof/CircleImageView.
在开始使用NavigationView之前,我们还需要提前准备好两个东西:menu和headerLayout。menu是用来在NavigationView中显示具体的菜单项的,headerLayout则是用来在NavigationView中显示头部布局的。
我们先来准备menu,这里我事先找了几张图片来作为按钮的图标,并将它们放在了mipmap-xxhdpi目录下。然后右击menu文件夹 —> New —> Menu resource file,创建一个nav_menu.xml文件,编写如下代码:
我们首先在<menu>中嵌套了一个<group>标签,然后将group的checkableBehavior属性指定为single。group表示一个组,checkableBehavior指定为single表示组中的所以菜单项只能单选。
那么接下来我们就看一下这些菜单项吧。这里一共定义了5个item,分别使用android:id属性指定菜单项的id,android:icon属性指定菜单项的图标,android:title属性指定菜单项显示的文字。现在我们就已经把menu准备好了。
接下来应该准备headerLayout了,这是一个可以随意定制的布局,为了简单起见,我们就在headerLayout中放置头像、用户名、邮箱地址这3项内容。这里找了一些宠物头像,并把它放置在了mipmap-xxhdpi目录下,另外这张图片最好是一张正方形图片,因为待会儿我们会它圆形化,然后点击layout文件夹 — New — Layout resource file,创建一个nav_header.xml文件。修改其中的内容,如下所示:
可以看到,布局文件的最外层是一个RelativeLayout,我们将它的宽度设为match_parent,高度设为180dp,这是一个NavigationView比较合适的高度,然后指定它的背景色为colorPrimary。
在RelativeLayout中我们放置了3个控件,CircleImageView是一个用于将图片圆形化的控件,它的用法非常简单,基本和ImageView是完全一样的,这里给它指定了一张图片作为头像,然后设置为居中显示。另外两个TextView分别用于显示用户名和邮箱地址,它们都用到了一些RelativeLayout的定位属性。
现在menu和headerLayout都准备好了,我们终于可以使用NavigationView了。修改activity_main.xml中的代码:
可以看到,我们将之前TextView换成了NavigationView,这样滑动菜单中显示的内容也就变成NavigationView了,这里又通过app:menu和app:headerLayout属性将我们刚才准备好的menu和headerLayout设置进去,这样NavigationView就定义完成了。
NavigationView虽然定义完成了,但是我们还要去处理菜单项的点击事件才行。修改MainActivity中的代码:
代码还是比较简单的,这里首先获取到了NavigationView的实例,然后调用它的setCheckedItem()方法将Call菜单项设置为默认选中。接着调用了setNavigationItemSelectedListener()方法来设置一个菜单项选中事件的监听器,当用户点击了任意菜单项时,就会回调到onNavigationItemSelected()方法中。我们可以在这个方法中写相应的逻辑处理,不过这里并没有附加任何逻辑,只是调用了DrawerLayout的closeDrawers()方法将滑动菜单关闭,这也是合情合理的做法。
现在运行程序,点击一下Toolbar左侧的导航按钮,效果如图所示:
这样的滑动菜单项页面看着就好很多了,Material Design的魅力就在这里,它真的是一种非常美观的设计理念,只要你按照它的各种规范和建议来设计界面,最终做出来的程序就是特别好看的。
五、悬浮按钮和可交互提示
立面设计是Material Design中一条非常重要的设计思想,也就是说,按照Material Design的理念,应用程序的界面不仅仅只是一个平面,而应该是有立体效果的。在官方给出的示例中,最简单最具代表性的立面设计就是悬浮按钮了,这种按钮不属于主界面平面的一部分,而是位于另外一个维度的,因此就会给人一种悬浮的感觉。
本节中会对这个悬浮按钮的效果进行学习,另外还会学习一种可交互的提示工具。关于提示工具,我们之前一直都是在使用的Toast,但是Toast只能用于告知用户某某事情已经发生,用户不能对此做出任何的响应。
5.1、FloatingActionButton
FloatActionButton是Design Support库中提供的一个控件,这个控件可以帮助我们比较轻松地实现悬浮按钮的效果。其实在之前我们已经预览过悬浮按钮是什么样子的了,它默认会使用colorAccent来作为按钮的颜色,我们还可以通过给按钮指定一个图标来表面这个按钮的作用是什么。
接下来具体实现:
第一步:首先准备好一个图标,这里我放置了一张ic_done.jpg到mipmap-xxhdpi目录下,然后修改activity_main.xml中的代码,如下所示:
这里在主屏幕布局中加入了一个FloatingActionButton。这个控件的用法并没有什么特别的地方,layout_width和layout_height属性都指定成wrap_content,layout_gravity属性指定将这个控件放置于屏幕的右下角,其中end的工作原理和之前的start是一样的,即如果系统语言是从左往右的,那么end就表示在右边,如果系统语言是从右往左的,那么end就表示在左边,然后通过layout_margin属性给控件的四周留点边距,紧贴着屏幕边缘肯定是不好看的,最后通过src属性给FloatingActionButton设置了一个图标。
第二步:运行程序,我们看到屏幕右下方出现了一个悬浮按钮。
仔细观察,会发现这个悬浮按钮的下面还有一点阴影,其实这个很好理解,因为FloatingActionButton是悬浮在当前界面上的,既然是悬浮,那么理所应当会有投影,Design Support库连这种细节都帮我们考虑到了。
说到悬浮,其实还可以指定FloatingActionButton的悬浮高度,如下所示:
这里使用app:elevation属性来给FloatingActionButton指定一个高度值,高度值越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围也越小,但是投影效果越浓。当然这些效果的差异其实都不怎么明显,默认使用FloatingActionButton效果就已经足够了。
接下来看一下FloatingActionButton是如何处理点击事件的,毕竟,一个按钮首先要能点击才有意义,修改MainActivity中的代码:
FloatingActionButton其实和普通的Button没什么两样,都是调用setOnClickListener()方法来注册一个监听器,当点击按钮时,就会执行监听器中的onClick()方法,这里我们在onClick()方法中弹出了一个Toast。
重新运行一下程序,点击FloatingActionButton,效果如下:
5.2、Snackbar
现在我们已经掌握了FloatingActionButton的基本用法,不过在上一节处理点击事件的时候,仍然是使用Toast来作为提示工具的,本小节来学习一下Design Support库提供的更加先进的提示工具——Snackbar。
首先要明确,Snackbar并不是Toast的替代品,它们两者之间有着不同的应用场景。Toast的作用是告诉用户现在发生了什么事情,但同时用户只能被动接收这个事情,因为没有什么办法能让用户进行选择。而Snackbar则在这方面进行了扩展,它允许在提示当中加入一个可交互按钮,当用户点击按钮的时候可以执行一些额外的逻辑操作,打个比方,如果我们在执行删除操作的时候只弹出一个Toast提示,那么用户要是误删了某个重要数据的话肯定十分抓狂,但是如果我们增加一个Undo按钮,就相当于给用户提供了一个弥补措施,从而大大降低了事故发生的概率,提升了用户体验。
Snackbar的用法也非常简单,它和Toast基本相似,只不过可以额外增加一个按钮的点击事件。修改MainActivity中的代码:
可以看到,这里调用了Snackbar的make()方法来创建一个Snackbar对象,make()方法的第一个参数需要传入一个View,只要是当前界面布局的任意一个View都可以,Snackbar会使用这个View来自动查找最外层的布局,用于展示Snackbar。第二个参数就是Snackbar中显示的内容,第三个参数是Snackbar显示的时长,这些都和Toast类似的。
接着这里又调用了一个setAction()方法来设置一个动作,从而让Snackbar不仅仅是一个提示,而是可以和用户进行交互的,简单起见,我们在动作按钮的点击事件里弹出一个Toast提示,最后调用show()方法让Snackbar显示出来。
重新运行程序,并点击悬浮按钮,效果如下:
可以看到,Snackbar从屏幕底部出现了,上面有我们所设置的提示文字,还有一个Undo按钮,按钮是可以点击的,过一段时间后Snackbar会自动从屏幕底部消失。
不管是出现还是消失,Snackbar都是带有动画效果的,因此视觉体验也会比较好。不过这里有一个bug,这个Snackbar竟然将我们的悬浮按钮给遮挡住了,虽说也不是什么重大的问题,因为Snackbar过一会儿就会自动消失,但这种用户体验总归是不友好的,需要借助下面的CoordinatorLayout来解决。
5.3、CoordinatorLayout
CoordinatorLayout可以说是一个加强版的FrameLayout,这个布局也是由Design Support库提供的。它在普通情况下的作用和FrameLayout基本一致,不过既然是Design Support库中提供的布局,那么必然有一些Material Design的魔力了。
事实上,CoordinatorLayout可以监听其他所有子控件的各种事件,然后自动帮助我们做出最为合理的响应。举个简单的例子,刚才弹出的Snackbar提示将悬浮按钮遮挡住了,而如果我们能让CoordinatorLayout监听到Snackbar的弹出事件,那么它会自动将内部的FloatingActionButton向上偏移,从而确保不会被Snackbar遮挡到。
至于CoordinatorLayout的使用也非常简单,我们只需要将原来的FrameLayout替换一下就可以了。修改activity_main.xml中的代码:
由于CoordinatorLayout本身就是一个加强版的FrameLayout,因此这种替换不会有任何的副作用。现在重新运行程序,并点击悬浮按钮,效果如下:
可以看到,悬浮按钮自动向上偏移了Snackbar的同等高度,从而确保不会被遮挡住,当Snackbar消失的时候,悬浮按钮会自动向下偏移回到原来的位置。
另外悬浮按钮的向上和向下偏移也是伴随着动画效果的,且和Snackbar完全同步,整体效果看上去特别赏心悦目。不过我们回过头来再思考一下,刚才说的是CoordinatorLayout可以监听其所有子控件的各种事件,但是Snackbar好像并不是CoordinatorLayout的子控件,为什么它却可以被监听到呢?
其实道理很简单,还记得在Snackbar的make()方法中传入的第一个参数吗?这个参数就是用来指定Snackbar是基于哪个View来触发的,刚才我们传入的是FloatingActionButton本身,而FloatingActionButton是CoordinatorLayout中的子控件,因此这个事件就理所应当能被监听到了。可以自己做个实验,如果给Snackbar的make()方法传入一个DrawerLayout,那么Snackbar就会再次遮挡住悬浮按钮,因为DrawerLayout不是CoordinatorLayout的子控件,CoordinatorLayout也就无法监听到Snackbar的弹出和隐藏事件了。
六、卡片式布局
虽然现在MaterialTest中已经应用了非常多的Material Design效果,不过你会发现,界面上最主要的一块区域还是处于空白状态,这块区域通常都是用来放置应用的主体内容的,这里准备使用一些精美的水果图片来填充这部分区域。
那么为了要让水果图片也能Material化,本节中我们将会学习如何实现卡片式布局的效果。卡片式布局也是Material Design中提出的一个新的概念,它可以让页面中的元素看起来就像在卡片中一样,并且还能拥有圆角和投影。
6.1、CardView
CardView是用于实现卡片式布局效果的重要控件,由appcompat-v7库提供。实际上,CardView也是一个FrameLayout,只是额外提供了圆角和阴影等效果,看上去会有立体的感觉。我们先来看一下CardView的基本用法吧:
这里定义了一个CardView布局,我们可以通过app:cardCornerRadius属性指定卡片圆角的弧度,数值越大,圆角的弧度也越大,另外还可以通过app:elevation属性指定卡片的高度,高度值越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围也越小,但是投影效果越浓,这一点和FloatingActionButton是一致的。
然后我们在CardView布局中放置了一个TextView,那么这个TextView就会显示在一张卡片当中了,CardView的用法就是这么简单。但是我们显然不可能在如此宽阔的一块空白区域内只放置一张卡片,为了能够充分利用屏幕的空间,这里准备综合运用一下前面的知识,使用RecycleView来填充MaterialTest项目的主界面部分,把之前的那个水果列表进行升级一下,实现一个高配版的水果列表。
既然要实现水果列表,那么首先是准备水果图片,将之前的水果图片复制到mipmap-xxhdpi目录下。然后由于我们还需要用到RecycleView、CardView这几个控件,因此必须要在app/build.gradle文件中声明这些库的依赖才行:
注意上述声明的最后一行,这里添加了一个Glide库的依赖,Glide是一个超级强大的图片加载库,它不仅可以用于加载本地图片,还可以加载网络图片、GIF图片、甚至是本地视频,最重要的是,Glide的用法非常简单,只需要一行代码就能轻松实现复杂的图片加载功能,因此这里就准备用它来加载水果图片。Glide的项目主页地址是:https://github.com/bumptech/glide。
接下来开始具体的代码实现,修改activity_main.xml中的代码,如下所示:
这里我们在CoordinatorLayout中添加了一个RecyclerView,给它指定了一个id,然后宽度和高度都设置为match_parent,这样RecyclerView也就占满了整个布局的空间。
接着定义一个实体类Fruit,代码如下所示:
Fruit类中只有两个字段,name表示水果的名字,imageId表示水果对应图片的资源id。
然后需要为RecyclerView的子项指定一个我们自定义的布局,在layout目录下新建fruit_item.xml:
这里使用了CardView来作为子项的最外层布局,从而使得RecyclerView中的每个元素都是在卡片当中的。CardView由于是一个FrameLayout,因此它没有什么方便的定位方式,这里我们只好在CardView中再嵌套一个LinearLayout,然后在LinearLayout中放置具体的内容。
内容倒也没有什么特殊的地方,就是定义了一个ImageView用于显示水果的图片,又定义了一个TextView用于显示水果的名称,并让TextView在水平方向上居中显示,注意在ImageView中我们使用了一个scaleType属性,这个属性可以指定图片的缩放模式。由于各张水果图片的长宽比例可能都不一致,为了让所有的图片都能填充满整个ImageView,这里使用了centerCrop模式,它可以让图片保持原有的比例填充满ImageView,并将超出屏幕的部分裁剪掉。
接下来需要为RecyclerView准备一个适配器,新建FruitAdapter类,继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder,代码如下:
上述代码已经很熟悉了,和之前编写的FruitAdapter几乎一模一样,唯一需要注意的是,在onBindViewHolder()方法中我们使用了Glide来加载水果的图片。关于Glide的用法,也没什么好讲的,因为Glide的用法实在是太简单了。首先调用Glide.with()方法并传入一个Context、Activity或Fragment参数,然后调用load()方法去加载图片,可以是一个URL地址,也可以是一个本地路径,或者是一个资源id,最后调用into()方法将图片设置到具体某一个ImageView中的就可以了。
为什么使用Glide而不是传统的设置图片方式呢?因为如果图片的像素都非常高,如果不进行压缩就直接展示的话,很容易就会引起内存溢出。而使用Glide就完全不需要担心,因为Glide在内部做了许多非常复制的逻辑操作,其中就包括图片压缩,我们只需要安心按照Glide的标准用法去加载图片就可以了。
这样我们就将RecyclerView的适配器也准备好了,最后修改MainActivity中的代码,如下所示:
代码分析:
在MainActivity中我们首先定义了一个数组,数组里面存放了很多个Fruit的实例,每个实例都代表着一种水果,然后再initFruit()方法中,先是清空了一下fruitList中的数据,接着使用一个随机函数,从刚才定义的Fruit数组中随机挑选一个水果放入到fruitList当中,这样每次打开程序看到的水果数据都会是不同的。另外为了让界面上的数据多一些,这里使用了一个循环,随机挑选50个水果。
之后的用法就是RecyclerView的标准用法了,不过这里使用了GridLayoutManager这种布局方式,前面已经学过了LinearLayoutManager和StaggeredGridLayoutManager,现在终于将所以的布局都补齐了,GridLayoutManager的用法也没有什么特别之处,它的构造函数接收两个参数,第一个是Context,第二个是列数,这里我们希望每一行中会有两列数据。
现在运行程序,效果如下:
可以看到,精美的水果图片成功的展示出来了,每个水果都是在一张单独的卡片当中的,并且还拥有圆角和投影,由于是使用随机的方式来获取水果数据的,因此界面上会有一些重复的水果出现,这是正常现象。
但是我们发现,Toolbar不见了,这是被RecyclerView给挡住了,为了解决这个问题,就需要借助下面需要学的另外一个工具了——AppBarLayout。
6.2、AppBarLayout
首先分析一下为什么RecyclerView会把Toolbar给遮挡住。由于RecyclerView和Toolbar都是放置在CoordinatorLayout中的,而前面说过,CoordinatorLayout就是一个加强版的FrameLayout,那么FrameLayout中的所有控件在不进行明确定位的情况下,默认都会摆放在布局的左上角,从而也就产生了遮挡的现象,其实这已经不是第一次遇到这种情况了,在学习FrameLayout的时候就已经见识过控件与控件之间遮挡的效果。
那么该如何解决这种问题呢?传统情况下,使用偏移是唯一的解决办法,即让RecyclerView向下偏移一个Toolbar高度,从而保证不会遮挡到Toolbar,不过我们使用的并不是普通的FrameLayout,而是CoordinatorLayout,因此自然会有一些更加巧妙的解决办法。
这里准备使用Design Support库中提供的另外一个工具——AppBarLayout,AppBarLayout实际上是一个垂直方向的LinearLayout,它在内部做了很多滚动事件的封装,并应用了一些Material Design的设计理念。
那么我们怎样使用AppBarLayout才能解决前面的覆盖问题呢?其实只需要两步就可以了。
第一步:将Toolbar嵌套到AppBarLayout中。
第二步:给RecyclerView指定一个布局行为。
修改activity_main.xml中的代码:
可以看到布局文件并没有什么大的变化,首先定义了一个AppBarLayout,并将Toolbar放置在了AppBarLayout里面,然后在RecyclerView中使用app:layout_behavior属性指定了一个布局行为,其中appbar_scrolling_view_behavior这个字符串也是由Design Support库提供的。
现在重新运行一下程序,我们看到一切都正常了:
虽说使用AppBarLayout已经成功解决了RecyclerView遮挡Toolbar的问题,但是刚才有提到过,说AppBarLayout中应用了一些Material Design的设计理念,好像从上面的例子中完全没有体现出来,事实上,当RecyclerView滚动的时候就已经将滚动事件都通知给AppBarLayout了,只是我们还没有进行处理而已。那么下面就来进一步优化,看看AppBarLayout到底能实现什么样的Material Design效果。
当AppBarLayout接收到滚动事件的时候,它的内部子控件其实是可以指定如何去影响这些事件的,通过app:layout_scrollFlags属性就能实现,修改activity_main.xml中的代码。如下所示:
这里在Toolbar中添加了一个app:layout_scrollFlags属性,并将这个属性的值指定成了scroll | enterAlways | snap。其中scroll表示当RecyclerView向上滚动的时候,Toolbar会跟着一起向上滚动并实现隐藏;enterAlways表示当RecyclerView向下滚动的时候,Toolbar会跟着一起向下滚动并重新显示。snap表示当Toolbar还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示。
只要改动这一行代码就行了,重新运行程序,并向上滚动RecyclerView,效果如下:
可以看到,随着我们向上滚动RecyclerView,Toolbar就消失了,而向下滚动RecyclerView,Toolbar又会重新出现。这其实也是Material Design中的一项重要设计思想,因为当用户在向上滚动RecyclerView的时候,其注意力肯定是在RecyclerView的内容上面,这个时候如果Toolbar还占据这屏幕空间,就会在一定程序上影响用户的阅读体验,而将Toolbar隐藏则可以让阅读体验达到最佳状态。当用户需要操作Toolbar上的功能时,只需要轻微向下滚动,Toolbar就会重新出现,这种设计方式,既保证了用户的最佳阅读效果,又不影响任何功能上的操作。
像这种功能如果使用ActionBar的话,那就完全不能实现了,Toolbar的出现为我们提供了更多的可能。
七、下拉刷新
下拉刷新这种功能几乎所有的应用里都会有这个功能,不过市面上现有的下拉刷新功能在风格上各有不同,并且和Material Design还有些格格不入的感觉。因此,谷歌为了让Android的下拉刷新风格有一个统一的标准,于是在Material Design中制定了一个官方的设计规范,当然,我们并不需要去深入了解这个规范到底是什么样的,因为谷歌早就提供好了现成的控件,我们只需要在项目中直接使用就可以了。
SwipeRefreshLayout就是用于实现下拉刷新功能的核心类,它是由support-v4库提供的。我们把想要实现下拉刷新功能的控件放置到SwipeRefreshLayout中,就可以迅速让这个控件支持下拉刷新。那么在MaterialTest项目中,应该支持下拉刷新功能的控件自然就是RecyclerView了。
由于SwipeRefreshLayout的用法也比较简单,下面我们就直接开始使用了。修改activity_main.xml中的代码,如下所示:
可以看到,这里我们在RecyclerView的外面又嵌套了一层SwipeRefreshLayout,这样RecyclerView就自动拥有下拉刷新功能了。另外需要注意,由于RecyclerView现在变成了SwipeRefreshLayout的子控件,因此之前使用app:layout_behavior声明的布局行为现在也移动到SwipeRefreshLayout中才行。
不过这还没结束,虽然RecyclerView已经支持下拉刷新功能了,但是我们还要在代码中处理具体的刷新逻辑才行。修改MainActivity中的代码:
这段代码应该还是比较好理解,首先通过findViewById()方法来拿到SwipeRefreshLayout的实例,然后调用setColorSchemeResources()方法来设置下拉刷新进度条的颜色,这里我们就使用主题中的colorPrimary作为进度条的颜色,接着调用setOnRefreshListener()方法来设置一个下拉刷新的监听器,当触发了下拉刷新操作的时候就会回调这个监听器的onRefresh()方法,然后我们在这里去处理具体的刷新逻辑就可以了。
通常情况下,onRefresh()方法中应该是去网络上请求最新的数据,然后再将这些数据显示出来,这里简单起见,我们就不和网络进行交互了,而是调用一个refreshFruits()方法进行本地刷新操作。refreshFruits()方法中先是开启了一个线程,然后将线程沉睡两秒钟。之所以这么做,是因为本地刷新操作速度非常快,如果不将线程沉睡的话,刷新立刻就结束了,从而看不到刷新的过程。沉睡结束后,这里使用了runOnUiThread()方法将线程切换回主线程,然后调用initFruit()方法重新生成数据,接着再调用FruitAdapter的notifyDataSetChanged()方法通知数据发生了变化,最后调用SwipeRefreshLayout的setRefreshing()方法并传入false,用于表示刷新事件结束,并隐藏刷新进度条。
重新运行程序,在屏幕的主界面向下拖动,会有一个下拉刷新的进度条出现,松手后就会自动进行刷新了,效果如下:
下拉刷新的进度条只会停留两秒钟,之后就会自动消失,界面上的水果数据也会随之更新。
这样我们就把下拉刷新的功能也成功实现了,并且这就是Material Design中规定的最标准的下拉刷新效果。目前我们的项目中已经应用了众多Material Design的效果,Design Support库中的常用控件也学了大半,接下来还要学一个非常震撼的Material Design效果——可折叠式标题栏。
八、可折叠式标题栏
虽说我们现在的标题栏是使用Toolbar来编写的,但是它看上去和传统的ActionBar其实没有什么两样,只不过可以响应RecyclerView的滚动事件来进行隐藏和显示,而Material Design中并没有限定标题栏必须是长什么样子的,事实上,我们可以根据自己的喜好随意定制标题栏的样式,这一节我们实现一个可折叠式标题栏的效果,需要借助于CollapsingToolbarLayout这个工具。
8.1、CollapsingToolbarLayout
顾名思义,CollapsingToolbarLayout是一个作用于Toolbar基础之上的布局,它也是由Design Support库提供的。CollapsingToolbarLayout可以让Toolbar的效果变得更加丰富,不仅仅是展示一个标题栏,而是能够实现非常华丽的效果。
不过,CollapsingToolbarLayout是不能独立存在的,它在设计的时候就被限定只能作为AppBarLayout的直接子布局来使用。而AppBarLayout又必须是CoordinatorLayout的子布局,因此本节中我们要实现的功能其实需要综合运用前面所学的各种的知识。
第一步:首先我们需要一个额外的活动来作为水果的详情展示界面,新建一个Empty Activity,命名为FruitActivity,并将布局名指定为activity_fruit.xml,然后我们开始编写水果详情展示界面的布局。由于整个布局文件比较复杂,这里准备采用分段编写的方式,activity_fruit.xml中的内容主要分为两部分,一个是水果标题栏,一个是水果内容详情,现在一步一步实现。
首先实现标题栏部分,这里使用CoordinatorLayout来作为最外层布局,如下所示:
一开始的代码还是比较简单的,相信没有什么需要解释的地方。注意始终记得要定义一个xmlns:app的命名空间,在Material Design的开发中会经常用到它。
接着我们在CoordinatorLayout中嵌套一个AppBarLayout,如下所示:
目前为止也没有什么难理解的地方,我们给AppBarLayout定义了一个id,将它的高度指定为match_parent,高度指定为250dp。当然这里的高度值可以随意指定。
接下来在AppBarLayout中再嵌套一个CollapsingToolbarLayout,如下所示:
从现在开始就有点难理解了,这里我们使用了新的布局CollapsingToolbarLayout,其中,id、layout_width和layout_height这几个属性比较简单。
android:theme属性指定了一个ThemeOverlay.AppCompat.Dark.ActionBar的主题,其实对于这部分也并不陌生,因为之前在activity_main.xml中给Toolbar指定的也是这个主题,只不过这里实现更加高级的Toolbar效果,因此需要将这个主题的指定提到上一层来。
app:contentScrim属性用于指定CollapsingToolbarLayout在趋于折叠状态以及折叠之后的背景色,其实CollapsingToolbarLayout在折叠之后就是一个普通的Toolbar,那么背景色肯定应该是colorPrimary了,具体的效果待会就能看到。
app:layout_scrollFlags属性也见过,只不过之前是Toolbar指定的,现在也移到外面来了,其中,scroll表示CollapsingToolbarLayout会随着水果内容详情的滚动一起滚动,exitUntilCollapsed表示当CollapsingToolbarLayout随着滚动完成折叠之后就保留在界面上,不再移出屏幕。
接下来在CollapsingToolbarLayout中定义标题栏的具体内容,如下所示:
可以看到,在CollapsingToolbarLayout中定义了一个ImageView和一个Toolbar,也就意味着,这个高级版的标题栏将是由普通的标题栏上图片组合而成的,这里定义的大多数属性都是见过的,只有一个app:layout_collapseMode比较陌生,它用于指定当前控件在CollapsingToolbarLayout折叠过程中的折叠模式,其中Toolbar指定成pin,表示在折叠的过程中位置始终保持不变,ImageView指定成parallax,表示会在折叠的过程中产生一定的错误偏移,这种模式的视觉效果会非常好。
这样我们就将水果的标题栏的界面编写好了。
第二步:接下来开始编写水果内容详情部分,继续修改activity_fruit.xml中的代码:
水果内容详情的最外层布局使用了一个NestedScrollView,注意它和AppBarLayout是平级的。之前学过ScrollView的用法,它允许使用滚动的方式来查看屏幕以外的数据,而NestedScrollView在此基础上还增加了嵌套响应滚动事件的功能。由于CoordinatorLayout本身已经可以响应滚动事件了,因此我们在它的内部就需要使用NestedScrollView或RecyclerView这样的布局,另外,这里还通过app:layout_behavior属性指定了一个布局行为,这和之前在RecyclerView中的用法是一模一样的。
不管是ScrollView还是NestedScrollView,它们的内部都只允许存在一个直接子布局,因此,如果我们想要在里面放入很多东西的话,通常都会先嵌套一个LinearLayout,然后再在LinearLayout中放入具体的内容就可以了,如下所示:
这里我们嵌套了一个垂直方向的LinearLayout,并将layout_width设置为match_parent,将layout_height设置为wrap_content。
接下来在LinearLayout中放入具体的内容,这里我准备使用一个TextView来显示水果的内容详情。并将TextView放在一个卡片式布局当中,如下所示:
这段代码也没有什么难理解的地方,都是学过的知识,需要注意的是,这里为了让界面更加美观,我在CardView和TextView上都加上了一些边距,其中,CardView的marginTop加了35dp的边距,这是为下面要编写的东西留出空间。
现在就把水果标题栏和水果内容详情的界面都编写完了,不过我们还可以在界面上再添加一个悬浮按钮,这个悬浮按钮并不是必需的,根据具体的需求添加就可以了,如果加入的话,将免费获得一些额外的动画效果。
为了示范,准备在activity_fruit.xml中加入一个悬浮按钮,这个界面是一个水果详情展示界面,那么这里就加入一个表示评论作用的悬浮按钮吧。首先需要提前准备好一个图标,这里放置了一张ic_comment.jpg到mipmap-xxhdpi目录下,然后修改activity_fruit.xml中的代码,如下所示:
可以看到,这里加入了一个FloatingActionButton,它和AppBarLayout以及NestedScrollView是平级的。FloatingActionButton中使用app:layout_anchor属性指定了一个锚点,我们锚点设置为AppBarLayout,这样悬浮按钮就会出现在水果标题栏的区域内,接着又使用app:layout_anchorGravity属性将悬浮按钮定位在标题栏区域的右下角,其他一些属性都比较简单,就不多说了。
到此为止,我们已经将整个activity_fruit.xml布局都编写完了,内容虽然比较长,但由于是分段编写的,并且每一步都进行了详细的说明。
第三步:界面完成之后,接下来我们开始编写功能逻辑,修改FruitActivity中的代码,如下所示:
代码分析:
FruitActivity中的代码并不是很复杂。首先在onCreate()方法中,我们通过Intent获取到传入的水果名和水果图片的资源id,然后通过findViewById()方法拿到刚才在布局文件中定义的各个控件的实例,接着就是使用Toolbar的标准用法,将它作为ActionBar显示,并启用HomeAsUp按钮,由于HomeAsUp按钮的默认图标就是一个返回箭头,这正是我们所期望的,因此就不用再额外设置别的图标了。
接下来开始填充界面上的内容,调用CollapsingToolbarLayout的setTitle()方法将水果名设置成当前界面的标题,然后使用Glide加载传入的水果图片,并设置到标题栏的ImageView上面。接着需要填充水果的内容详情,由于这只是一个示例程序,并不需要什么真实的数据,所以这里使用了一个generateFruitContent()方法将水果名循环拼接500次,从而生成一个比较长的字符串,将它设置到了TextView上面。
最后,在onOptionsItemSelected()方法中处理了HomeAsUp按钮的点击事件,当点击了这个按钮时,就调用finish()方法关闭当前的活动,从而返回上一个活动。
第四步:处理RecyclerView的点击事件,修改FruitAdapter中的代码:
这是最关键的一步,这里给CardView注册了一个点击事件监听器,然后在点击事件获取当前点击项的水果名和水果图片资源id,把它们传入到Intent中,最后调用startActivity()方法启动FruitActivity。
第五步:运行程序,并点击界面上的任意一个水果,效果如下:
这个界面上的内容分为三部分,水果标题栏、水果内容详情和悬浮按钮。Toolbar和水果背景图完美地融合到了一起,既保证了图片的展示空间,又不影响Toolbar的任何功能。
不过这并不是全部,尝试向上拖动水果内容详情,会发现背景图上的标题会慢慢缩小,并且背景图会产生一些错位偏移的效果。这是由于用户想要查看水果的内容详情,此时界面的重点在具体的内容上面,因此标题栏就会自动进行折叠,从而节省屏幕空间。继续向上拖动,直到标题栏变成完全折叠状态,如下所示,可以看到标题栏的背景图片不见了,悬浮按钮也自动消失了,现在的水果标题栏变成了一个普通的Toolbar,这是由于用户正在阅读具体的内容,需要给他们提供最充分的阅读空间。而如果这个时候向下拖动水果内容详情,就会执行一个完全相反的动画过程,最终恢复原样。
8.2、充分利用系统状态栏空间
虽说现在水果详情展示界面的效果已经非常华丽了,但是这并不代表不能再进一步地提升,如果将背景图片和状态栏融合到一起,那视觉体验绝对会提升好多档次。只不过可惜的是,在Android5.0之前,我们是无法对状态栏的背景或颜色进行操作的,那个时候也还没有Material Design的概念。但是在Android5.0之后的系统都是支持这个功能的,因此这里就来实现一个系统差异型的效果,在Android5.0之后的系统中,使用背景图和状态栏融合的模式,在之前的系统中使用普通的模式。
想要让背景图能够和系统状态栏融合,需要借助android:fitsSystemWindows这个属性来实现。在CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout这种嵌套结构的布局中中,将看见的android:fitsSystemWindows属性指定成true,就表示该控件会出现在系统状态栏里,对应到我们的程序,那就是水果标题栏中的ImageView应该设置这个属性了。不过只给ImageView设置这个属性是没有用的,我们必须将ImageView布局结构中的所有父布局都设置上这个属性才可以,修改activity_fruit.xml中的代码,如下所示:
但是,即使我们将android:fitsSystemWindows属性都设置好了还是没有用的,因为必须在程序的主题中将状态栏颜色指定成透明色才行。指定成透明色的方法很简单,在主题中将android:statusBarColor属性的值指定成@android:color/transparent就可以了,但问题在于,android:statusBarColor这个属性是从API21,也就是Android5.0系统开始有的,之前的系统无法指定这个属性,那么系统差异型的功能实现就要从这里开始了。
右击res目录 —> New —> Directory,创建一个values-v21目录,然后右击values-v21目录 —> New —> Values resource file,创建一个styles.xml文件,接着对这个文件进行编写,代码如下:
这里我们定义了一个FruitActivityTheme主题,它是专门给FruitActivity使用的。FruitActivityTheme的parent主题是AppTheme,也就是说,它继承了AppTheme中的所有特性。然后我们在FruitActivityTheme中将状态栏的颜色指定成透明色,由于values-v21目录是只有Android5.0及以上的系统才会去读取的,因此这么声明是没有问题的。
但是Android5.0之前的系统却无法识别FruitActivityTheme这个主题,因此我们还需要对Values/styles/xml文件进行修改,如下所示:
可以看到,这里也定义了一个FruitActivityTheme主题,并且parent主题也是AppTheme,但是它的内部是空的,因为Android5.0之前的系统无法指定状态栏的颜色,因此这里什么都不用做就可以了。
最后我们还需要让FruitActivity使用这个主题才可以,修改AndroidManifest.xml中的代码,如下所示:
这里使用了android:theme属性单独给FruitActivity指定了FruitActivityTheme这个主题,这样就大功告成了。现在只有运行在Android5.0以上的系统运行MaterialTest程序,水果详情展示界面的效果就会如图所示:
九、总结
本节的知识点比较多,充分利用了Design Support库、support-v4库、appcompat-v7库,以及一些开源项目来实现了一个高度Material化的应用程序,能将这些库中的相关控件熟练掌握。实际上,Material Design的设计思维和设计理念才是更加重要的东西,可以参考Material Design的官方文章:http://design.1sters.com/.
Android学习之基础知识十五 — 最佳UI体验(Material Design实战)的更多相关文章
- Android学习之基础知识十二 — 第一讲:网络技术的使用
这一节主要讲如何在手机端使用HTTP协议和服务器端进行网络交互,并对服务器返回的数据进行解析,这也是Android中最常用的网络技术. 一.WebView的用法 有时候我们可能会碰到比较特殊的需求,比 ...
- Android学习之基础知识十二 — 第二讲:网络编程的最佳实践
上一讲已经掌握了HttpURLConnection和OkHttp的用法,知道如何发起HTTP请求,以及解析服务器返回的数据,但是也许你还没发现,之前我们的写法其实是很有问题的,因为一个应用程序很可能会 ...
- Android学习之基础知识十—内容提供器(Content Provider)
一.跨程序共享数据——内容提供器简介 内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能 ...
- Android学习之基础知识十六 — Android开发高级技巧的掌握
一.全局获取Context的技巧 前面我们很多地方都使用到了Context,弹出Toast的时候.启动活动的时候.发送广播的时候.操作数据库的时候.使用通知的时候等等.或许目前来说我们并没有为得不到C ...
- Android学习之基础知识十四 — Android特色开发之基于位置的服务
一.基于位置的服务简介 LBS:基于位置的服务.随着移动互联网的兴起,这个技术在最近的几年里十分火爆.其实它本身并不是什么时髦的技术,主要的工作原理就是利用无线电通讯网络或GPS等定位方式来确定出移动 ...
- Android学习之基础知识十三 — 四大组件之服务详解第一讲
一.服务是什么 服务(Service)是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务.服务的运行不依赖于任何用户界面,即使程序被切换到后台, ...
- Android学习之基础知识七—碎片的最佳实践
一.Android碎片(Fragment)的最佳实践——简易版新闻应用 第一步:新建FragmentBestPractice项目,在app/build.gradle当中添加:RecyclerView ...
- Android学习之基础知识五—创建自定义控件
下面是控件和布局的继承关系: 从上面我们看到: 1.所有控件都是直接或间接继承View,所有的布局都是直接或间接继承ViewGroup 2.View是Android中最基本的UI组件,各种组件其实就是 ...
- Android学习之基础知识五—Android常用的七大控件
一.TextView控件:在界面上显示一段文本信息 先看XML代码和执行效果: 代码分析: 1.android:id属性,给当前控件定义了一个唯一的标识符 2.android:layo ...
随机推荐
- javascript基于对象的弹出框封装
先睹为快,移动端:戳这里,打开页面后点击投票查看效果.PC端测试直接切换body的overflow属性:hidden和auto一样可以,比下面相对简化,又有人说这样偶尔不行..如果你知道优缺点欢迎给出 ...
- Starting zookeeper ... already running as process 1805错误
启动zookeeper的时候,报Starting zookeeper ... already running as process 1805错误 上面这个错误意思为以作为进程1805运行.系统检测到你 ...
- CSS属性之position讲解
postion 属性定义了一个元素在页面布局中的位置以及对周围元素的影响.该属性共有5个值: position: static position: inherit position: relative ...
- webpack打包时排除其中一个css、js文件,或单独打包一个css、js文件
在项目中经常会需要将一些接口的配合文件或者某些样式文件,分离出来单独打包,便于后期改动,这里我以css文件为例,介绍实现两种方法: 项目目录: 如上图所示,现在我需要将项目中的scBtn.css文件单 ...
- js发送请求
1.Chrome控制台中 net::ERR_CONNECTION_REFUSED js频繁发送请求,有可能连接被拒绝,可用setTimeout,过几秒发送,给个缓冲时间 var overlayAnal ...
- 为什么radio没有出现单选效果?
原因是radio一定要设置相同的name,如下: <input type="radio" name="yunsuan" checked="che ...
- FI / CO 配置步骤清单
一.FI配置 01. 创建公司代码:企业结构-定义-财务会计-编辑/复制/删除/检查公司代码.编辑公司OX02 02. 创建会计科目表 OB13 03. 定义会计年度变式 OB29 04. 创建信贷控 ...
- python自动化开发-6-常用模块-续
python的常用模块(续) shelve模块:是一个简单的k,v将内存数据通过文件持久化的模块,可以持久化任何pickle可支持的python数据格式. configparser模块:对配置文件进行 ...
- 用PRODUCT_COPY_FILES拷贝文件夹
拷贝文件PRODUCT_COPY_FILES += device/qcom/msm8909/media/media_profiles_8909.xml:system/etc/media_profile ...
- Excel快捷键大全 Excel2013/2010/2007/2003常用快捷键大全
一个软件最大的用处是提高工作效率,衡量一个软件的好坏,除了是否出名之外,最主就是能否让一个新手更快的学会这个软件和提高工作速度.就拿Excel表格来说吧,平常办公中我们经常会用它来制作表格,统计数据或 ...