其实长久以来,大多数人都认为Android系统的UI并不算美观,至少没有iOS系统的美观。 以至于很多IT公司在进行应用界面设计的时候,为了保证双平台的统一性,强制要求Android 端的界面风格必须和iOS端一致。这种情况在现实工作当中实在是太常见了,虽然我认为这是非常不合理的。因为对于一般用户来说,他们不太可能会在两个操作系统上分别去使用同一个应用, 但是却必定会在同一个操作系统上使用不同的应用。因此,同一个操作系统中各个应用之间的界面统一性要远比一个应用在双平台的界面统一性重要得多,只有这样,才能给使用者带来更好的用户体验。

但问题在于,Android标准的界面设计风格并不是特别被大众所接受,很多公司都觉得自己完全可以设计出更加好看的界面,从而导致Android平台的界面风格长期难以得到统一。为了解决这个问题,谷歌也是祭出了杀手铜,在2014年Google I/O大会上重磅推出了一套全新的界面设计语言 Material Design。

本章我们就将对Material Design进行一次深入的学习。

12.1 什么是 Material Design

        Material Design是由谷歌的设计工程师们基于传统优秀的设计原则,结合丰富的创意和科学技术所发明的一套全新的界面设计语言,包含了视觉、运动、互动效果等特性。那么谷歌凭什么认为Material Design就能解决Android平台界面风格不统一的问题呢? 一言以蔽之, 好看!

        没错,这次谷歌在界面设计上确实是下足了功夫,很多媒体评论,Material Design的出现使得Android首次在UI方面超越了 iOS。按照正常的思维来想,如果各个公司都无法设计出比Material Design更出色的界面风格,那么它们就应该理所当然地使用Material Design来设计界面, 从而也就能解决Android平台界面风格不统一的问题了。

        为了做出表率,谷歌从Android 5.0系统开始,就将所有内置的应用都使用Material Design 风格来进行设计。这里我随便截了两张图,你可以先欣赏一下,如图12.1所示。

         其中,左边的应用是Play Store,右边的应用是YouTubeo可以看出,它们的界面都十分美观,而它们正是使用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项目,然后我们马上开始吧!

12.2 Toolbar

        Toolbar将会是我们接触的第一个Material控件。虽说对于Toolbar你暂时应该还是比较陌生 的,但是对于它的另一个相关控件ActionBar,你就应该有点熟悉了。

        回忆一下,我们曾经在3.4.1小节为了使用一个自定义的标题栏,而把系统原生的ActionBar 隐藏掉。没错,每个活动最顶部的那个标题栏其实就是ActionBar,之前我们编写的所有程序里 一直都有ActionBar的身影。

        不过ActionBar由于其设计的原因,被限定只能位于活动的顶部,从而不能实现一些Material Design的效果,因此官方现在已经不再建议使用ActionBar To那么本书中我也就不准备再介绍 ActionBar的用法了 ,而是直接讲解现在更加推荐使用的Toolbar。

        Toolbar的强大之处在于,它不仅继承了 ActionBar的所有功能,而且灵活性很高,可以配合 其他控件来完成一些Material Design的效果,下面我们就来具体学习一下。

        首先你要知道,任何一个新建的项目,默认都是会显示ActionBar的,这个想必你已经见识 过太多次了。那么这个ActionBar到底是从哪里来的呢?其实这是根据项目中指定的主题来显示 的,打开AndroidManifest.xml文件看一下,如下所示:

<applicationandroid :allowBackup=,,true,'android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:supportsRtl="true"android:theme="@style/AppTheme">
</application>

        可以看到,这里使用android: theme属性指定了一个AppTheme的主题。那么这个AppTheme 又是在哪里定义的呢?打开res/values/styles.xml文件,代码如下所示:

<resources><! - - Base application theme.--><style name="AppTheme" parent=HTheme.AppCompat.Light.DarkActionBar,^<! - - Customize your theme here.--><item name="colorPrimary">@color/colorPrimary</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item><item name="colorAccent,,>(aco'lor/colorAccent</item> </style>
</resources>

        这里定义了一个叫AppTheme的主题,然后指定它的parent主题是Theme.AppCompat.Light. DarkActionBaro这个DarkActionBar是一个深色的ActionBar主题,我们之前所有的项目中自带 的ActionBar就是因为指定了这个主题才出现的。

        而现在我们准备使用Toolbar来替代ActionBar,因此需要指定一个不带ActionBar的主题, 通常有 Theme.AppCompat.NoActionBar 和 Theme.AppCompat.Light.NoActionBar 这两种主题可选。 其中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">(acolor/colorPrimary</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item><item name="colorAccent">(aco'Lor/colorAccent</item> </style>
</resources>

        然后观察一下AppTheme中的属性重写,这里重写了 colo「Primary、colorPrimaryDark 和colorAccent这3个属性的颜色。那么这3个属性分别代表着什么位置的颜色呢?我用语言 比较难描述清楚,还是通过一张图来理解一下吧,如图12.2所示。

         可以看到,每个属性所指定颜色的位置直接一目了然了。

        除了上述3个属性之外,我们还可以通过textColorPrimary. windowBackground和 navigationBarColor等属性来控制更多位置的颜色。不过唯独colorAccent这个属性比较难 理解,它不只是用来指定这样一个按钮的颜色,而是更多表达了一个强调的意思,比如一些控件 的选中状态也会使用colorAccent的颜色。

        现在我们已经将ActionBar隐藏起来了,那么接下来看一看如何使用Toolbar来替代 ActionBar0修改activity main.xml中的代码,如下所示:

<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.Toolbarandroid: idQQ+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>

        虽然这段代码不长,但是里面着实有不少技术点是需要我们去仔细琢磨一下的。首先看一下 第2行,这里使用xmlns:app指定了一个新的命名空间。思考一下,正是由于每个布局文件都 会使用xmlns:android来指定一个命名空间,因此我们才能一直使用android:id. android: layout width等写法,那么这里指定了 xmlns:app,也就是说现在可以使用app:attribute 这样的写法了。但是为什么这里要指定一个xmlns: app的命名空间呢?这是由于Material Design 是在Android 5.0系统中才出现的,而很多的Material属性在5.0之前的系统中并不存在,那么为 了能够兼容之前的老系统,我们就不能使用android attribute这样的写法了,而是应该使用 app:attributeo

        接下来定义了一个Toolbar控件,这个控件是由appcompat-v7库提供的。这里我们给Toolbar 指定了一个id,将它的宽度设置为match_pa「ent,高度设置为actionBar的高度,背景色设置为 colorPrimaryo不过下面的部分就稍微有点难理解了,由于我们刚才在styles.xml中将程序的主题 指定成了淡色主题,因此Toolbar现在也是淡色主题,而Toolbar±面的各种元素就会自动使用深 色系,这是为了和主体颜色区别开。但是这个效果看起来就会很差,之前使用ActionBar时文字 都是白色的,现在变成黑色的会很难看。那么为了能让Toolbar单独使用深色主题,这里我们使 用 android: theme 属性,将 Toolbar 的主题指定成了 ThemeOverlay.AppCompat.Dark.ActionBaro 但是这样指定完了之后又会岀现新的问题,如果Toolbar中有菜单按钮(我们在2.2.5小节中学 过),那么弹出的菜单项也会变成深色主题,这样就再次变得十分难看,于是这里使用了 app: popupTheme属性单独将弹出的菜单项指定成了淡色主题。之所以使用app: popupTheme, 是因为popupTheme这个属性是在Android 5.0系统中新增的,我们使用app:popupTheme的话 就可以兼容Android 5.0以下的系统了。

        如果你觉得上面的描述很绕的话,可以自己动手做一做试验,看看不指定上述主题会是什么 样的效果,这样你会理解得更加深刻。

写完了布局,接下来我们修改MainActivity,代码如下所示:

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedlnstanceState) {         super.onCreate(savedlnstanceState); setContentView(R.layout.activitymain); Toolbar         toolbar = (Toolbar) findViewByldfR.id.toolbar); setSupportActionBar(toolbar);
}
}

        这里关键的代码只有两句,首先通过findViewByldO得到Toolbar的实例,然后调用setSupportActionBar()方法并将Toolbar的实例传入,这样我们就做到既使用了 Toolbar,又 让它的外观与功能都和ActionBar —致了。

        现在运行一下程序,效果如图12.3所示。

        这个标题栏我们再熟悉不过了,虽然看上去和之前的标题栏没什么两样,但其实它已经是 Toolbar而不是ActionBar 了。因此它现在也具备了实现Material Design效果的能力,这个我们在 后面就会学到。

        接下来我们再学习一些Toolbar比较常用的功能吧,比如修改标题栏上显示的文字内容。这 段文字内容是在AndroidManifest.xml中指定的,如下所示:

<applicationandroid:afl owBackup="true"android:icon="@mipmap/ic_launche android:'Label=,,@string/app name"         android:support5Rtl="true" android: theme="(astyle/AppTheme,,> <activityandroid:name=".MainActivity"android:label="Fruits"></activity>
</application>

        这里给activity增加了一个android: label属性,用于指定在Toolbar中显示的文字内容, 如果没有指定的话,会默认使用application中指定的label内容,也就是我们的应用名称。

        不过只有一个标题的Toolbar看起来太单调了,我们还可以再添加一些action按钮来让 Toolbar更加丰富一些,这里我提前准备了几张图片来作为按钮的图标,将它们放在了 drawable-xxhdpi目录下。现在右击res目>New~^Directory,创建一个menu文件夹。然后右击 menu文件夹—>New—>Menu resource file,创建一个toolbar.xml文件,并编写如下代码:

<menu xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"><itemandroid:id="@+id/backup"android:icon="@drawable/icbackup" android:title="Backup" app:showAsAction="always" /><itemandroid:id="@+id/delete"android:icon="@drawable/ic_delete"android:title="Delete"app:showAsAction="ifRoom" /><itemandroid:id="@+id/settings"android:icon="@drawable/icsettings"android:title="Settings" app:showAsAction="never" />
</menu>

        可以看到,我们通过<item>标签来定义action按钮,android:id用于指定按钮的id, android:icon用于指定按钮的图标,android:title用于指定按钮的文字。

        接着使用app: showAsAction来指定按钮的显示位置,之所以这里再次使用了 app命名空间, 同样是为了能够兼容低版本的系统。showAsAction主要有以下几种值可选:always表示永远显 示在Toolbar中,如果屏幕空间不够则不显示;ifRoom表示屏幕空间足够的情况下显示在Toolbar 中,不够的话就显示在菜单当中;never•则表示永远显示在菜单当中。注意,Toolbar中的action 按钮只会显示图标,菜单中的action按钮只会显示文字。

        接下来的做法就和2.2.5小节中的完全一致了,修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {
public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R・menu.toolbar, menu);return true;
©Override
public boolean onOptionsItemSelected(Menuitem
switch (item.getltemldO) {
case R.id.backup:
Toast.makeText(this, "You clicked show();
break; case R.id.delete:
Toast.makeText(this, "You clicked show();
break; case R.id.settings:
Toast.makeText(this, "You clicked show();
break; default:
} return true;

        非常简单,我们在onCreateOptionsMenu()方法中加载了 toolbar.xml这个菜单文件,然后 在0nOptionsItemSelected()方法中处理各个按钮的点击事件。现在重新运行一下程序,效果 如图12.4所示。

         可以看到,Toolbar ±面现在显示了两个action按钮,这是因为Backup按钮指定的显示位置

        是always, Delete按钮指定的显示位置是ifRoom,而现在屏幕空间很充足,因此两个按钮都会显 示在Toolbar中。另外一个Settings按钮由于指定的显示位置是never,所以不会显示在Toolbar 中,点击一下最右边的菜单按钮来展开菜单项,你就能找到Settings按钮了。另外这些action按 钮都是可以响应点击事件的,你可以自己去试一试。

        好了,关于Toolbar的内容就先讲这么多吧。当然Toolbar的功能还远远不只这些,不过我们 显然无法在一节当中就把所有的用法全部学完,后面会结合其他控件来挖掘Toolbar的更多功能。

12.3滑动菜单

        滑动菜单可以说是Material Design中最常见的效果之一了,在许多著名的应用(如Gmail、 Google+等)中,都有滑动菜单的功能。虽说这个功能看上去好像挺复杂的,不过借助谷歌提供 的各种工具,我们可以很轻松地实现非常炫酷的滑动菜单效果,那么我们马上开始吧。

  1. DrawerLayout

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

        不过如果我们全靠自己去实现上述功能的话,难度恐怕就很大了。幸运的是,谷歌提供了一 个DrawerLayout控件,借助这个控件,实现滑动菜单简单又方便。

        先来简单介绍一下DrawerLayout的用法吧。首先它是一个布局,在布局中允许放入两个直 接子控件,第一个子控件是主屏幕中显示的内容,第二个子控件是滑动菜单中显示的内容。因此, 我们就可以对activity main.xml中的代码做如下修改:

<android.support.v4・ widget.DrawerLayout xmlns:android=l,http://schemas,         android.com/apk/res/android" xmtns:app=ilhttp://schemas .android.com/apk/res-auto"         android:id="@+id/d rawe r_layout" android:tayout_width="match_parent" android:     layout__height="match_parent"><FrameLayoutandroid: layout_width=,,match_parent" android: layout_height=,,match_parent"><android.support.v7.widget.Toolbarandroid:id="@+id/toolbar"android: layout_width=,,match_parent"android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/ThemeOve rlay.AppCompat.Dark.ActionBar" app:     popupTheme="(astyle/ThemeOverlay. AppCompat. Light" /></FrameLayout><TextViewandroid: ■Layout__width="match_parent" android: ■Layout_height="match_parent"     android:layout_gravity="start" android:text=HThis is menu"android :textSize=l>30sp"android:background="#FFF" />
</android.support.v4.widget・ DrawerLayout>

        可以看到,这里最外层的控件使用了 DrawerLayout,这个控件是由support-v4库提供的。 DrawerLayout中放置了两个直接子控件,第一个子控件是FrameLayout,用于作为主屏幕中显 示的内容,当然里面还有我们刚刚定义的Toolbar0第二个子控件这里使用了一个TextView,用 于作为滑动菜单中显示的内容,其实使用什么都可以,DrawerLayout并没有限制只能使用固定 的控件。

        但是关于第二个子控件有一点需要注意,layout_gravity这个属性是必须指定的,因为我 们需要告诉DrawerLayout滑动菜单是在屏幕的左边还是右边,指定left表示滑动菜单在左边,指 定right表示滑动菜单在右边。这里我指定了 start,表示会根据系统语言进行判断,如果系统语 言是从左往右的,比如英语、汉语,滑动菜单就在左边,如果系统语言是从右往左的,比如阿拉 伯语,滑动菜单就在右边。

        没错,只需要改动这么多就可以了,现在重新运行一下程序,然后在屏幕的左侧边缘向右拖 动,就可以让滑动菜单显示出来了,如图12.5所示。

         然后向左滑动菜单,或者点击一下菜单以外的区域,都可以让滑动菜单关闭,从而回到主界 面。无论是展示还是隐藏滑动菜单,都是有非常流畅的动画过渡的。

        可以看到,我们只是稍微改动了一下布局文件,就能实现如此炫酷的效果,是不是觉得挺激 动呢?不过现在的滑动菜单还有点问题,因为只有在屏幕的左侧边缘进行拖动时才能将菜单拖 出来,而很多用户可能根本就不知道有这个功能,那么该怎么提示他们呢?

        Material Design建议的做法是在Toolbar的最左边加入一个导航按钮,点击了按钮也会将滑 动菜单的内容展示出来。这样就相当于给用户提供了两种打开滑动菜单的方式,防止一些用户不 知道屏幕的左侧边缘是可以拖动的。

        下面我们开始来实现这个功能。首先我准备了一张导航按钮的图标ic_menu.png,将它放在 T drawable-xxhdpi目录下。然后修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {
private DrawerLayout mDrawerLayout;
@0verride
protected void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.activitymain); Toolbar toolbar = (Toolbar) findViewByld(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);
} _
}
(aOverride
public boolean onOptionsItemSelected(Menuitem item) { switch (item.getltemld()) {
case and roid.R.id.home:
mDrawerLayout.openDrawer(GravityCompat.START); break;
default:
}
return true;
}
}

        这里我们并没有改动多少代码,首先调用findViewByldO方法得到了 DrawerLayout的实 例,然后调用getSupportActionBar()方法得到了 ActionBar的实例,虽然这个ActionBar的具 体实现是由Toolbar来完成的。接着调用ActionBar的setDisplayHomeAsUpEnabled()方法让导 航按钮显示出来,又调用了 setHomeAsUpIndicator()方法来设置一个导航按钮图标。实际上, Toolbar最左侧的这个按钮就叫作HomeAsUp按钮,它默认的图标是一个返回的箭头,含义是返 回上一个活动。很明显,这里我们将它默认的样式和作用都进行了修改。

        接下来在onOptionsItemSelectecK)方法中对HomeAsUp按钮的点击事件进行处理, HomeAsUp按钮的 id永远都是 android .R.id. home0 然后调用 DrawerLayout 的 openDrawer() 方法将滑动菜单展示出来,注意openDrawer()方法要求传入一个Gravity参数,为了保证这里 的行为和XML中定义的一致,我们传入了 GravityCompat.STARTo

        现在重新运行一下程序,效果如图12.6所示。

        可以看到,在Toolbar的最左边出现了一个导航按钮,用户看到这个按钮就知道这肯定是可 以点击的。现在点击一下这个按钮,滑动菜单界面就会再次展示出来了。

  1. NavigationView

        目前我们已经成功实现了滑动菜单功能,其中滑动功能已经做得非常好了,但是菜单却还很 丑,毕竟菜单页面仅仅使用了一个TextView,非常单调。有对比才会有落差,我们看一下Google+ 的滑动菜单页面是长什么样的,如图12.7所示。

        经过对比之后是不是觉得我们的滑动菜单页面更丑了?不过没关系,优化滑动菜单页面,这 就是我们本小节的全部目标。

        事实上,你可以在滑动菜单页面定制任意的布局,不过谷歌给我们提供了一种更好的方法一一 使用NavigationView o Navigation View是Design Support库中提供的一个控件,它不仅是严格按 照Material Design的要求来进行设计的,而且还可以将滑动菜单页面的实现变得非常简单。接下 来我们就学习一下NavigationView的用法。

        首先,既然这个控件是Design Support库中提供的,那么我们就需要将这个库引入到项目中 才行。打开app/build.gradle文件,在dependencies闭包中添加如下内容:

dependencies (compile fileTree(dir: 'libs 1, include: ['*.jar1])compile 'com.android.support:appcompat-v7:24.2.1' testCompile 'junit:junit:4.12'compile 'com・android.support:design:24.2.1* compile 'de・    hdodenhof:circleimageview:2.1.0*
}

        这里添加了两行依赖关系,第一行就是Design Support库,第二行是一个开源项目 CirclelmageView,它可以用来轻松实现图片圆形化的功能,我们待会就会用到它o Circlelmage View 的项目主页地址是:https://github.com/hdodenhof7CirclelmageViewo

        在开始使用NavigationView之前,我们还需要提前准备好两个东西:menu和headerLayouto menu是用来在NavigationView中显示具体的菜单项的,headerLayout则是用来在NavigationView 中显示头部布局的。

        我们先来准备menu,这里我事先找了几张图片来作为按钮的图标,并将它们放在了

        drawable-xxhdpi 目录下。然后右击 menu 文件夹—New—>Menu resource file ,创建一个 nav menu.xml文件,并编写如下代码:

<menu xmlns:android="http://schemas.android.com/apk/res/android">

<group android:checkableBehavior="single">

<item

and roid: id=,,@+id/nav_call"

android:icon="@drawable/nav_call"

android:title="Call" />

<item

android:id="@+id/nav_friends"

android: icon="(acl rawable/navf riends" android:title="Friends" />

<item

and roid: id="@+id/nav_'location"

android:icon="@drawable/nav_location"

android:title="Location" />

<item

android: id="(a+icl/nav_mail"

and roid: icon="(ad rawable/navmail"

android:title="Mail" />

<item

android: id=,,(a+id/nav_task"

android: icon=n(aclrawable/nav_task"

android:tifle="Tasks" />

</group>

</menu>

        我们首先在<menu>中嵌套了一个<group>标签,然后将group的checkableBehavior属性 指定为single。group表示一个组,checkableBehavior指定为single表示组中的所有菜单 项只能单选。

        那么下面我们来看一下这些菜单项吧。这里一共定义了 5个item,分别使用android:id属 性指定菜单项的id, android:icon属性指定菜单项的图标,android:title属性指定菜单项显 示的文字。就是这么简单,现在我们已经把menu准备好了。

        接下来应该准备headerLayout 了,这是一个可以随意定制的布局,不过我并不想将它做得太 复杂。这里简单起见,我们就在headerLayout中放置头像、用户名、邮箱地址这3项内容吧。

        说到头像,那我们还需要再准备一张图片,这里我找了一张宠物图片,并把它放在了 drawable-xxhdpi目录下。另外这张图片最好是一张正方形图片,因为待会我们会把它圆形化。然 后右击layout文件夹—>New-^Layout resource file,创建一个nav header.xml文件。修改其中的代 码,如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="180dp"

android: padding=,,10dp,*

android:background="?attr/colorPrimary">

<de.hdodenhof.circleimageview.CirclelmageView

android: id=,,@+id/icon_image"

android:layout_width="70dp"

android:layout_height="70dp"

android:src="@drawable/nav_icon"

android:layout_centerInParent="true" />

<TextView

android: id=,,@+id/username"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentBottom="true"

and roid: text="tonyg reendev(agmail. com"

android:textColor="#FFF"

android:textSize="14sp" />

<TextView

android:id="@+id/mail"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android: layout_above="(aid/usemame"

android:text="Tony Green"

android:textColor="#FFF"

android:textSize="14sp" />

</RelativeLayout>

        可以看到,布局文件的最外层是一个RelativeLayout,我们将它的宽度设为match_pa「ent, 高度设为180dp,这是一个NavigationView比较适合的高度,然后指定它的背景色为colorPrimary0

        在RelativeLayout中我们放置了 3个控件,CirclelmageView是一个用于将图片圆形化的控件, 它的用法非常简单,基本和ImageView是完全一样的,这里给它指定了一张图片作为头像,然后 设置为居中显示。另外两个TextView分别用于显示用户名和邮箱地址,它们都用到了一些 RelativeLayout的定位属性,相信肯定难不倒你吧?

        现在menu和headerLayout都准备好了,我们终于可以使用NavigationView 了。修改 activity main.xml中的代码,如下所示:

<android. support .v4.widget.Drav/erLayout

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/d rawe rlayout" android:layout_width="matchparent" android:layout_height="match_parent">

<FrameLayout

android:layoutwidth="matchparent"

android: layout_height=,,match_parent">

<android.support.v7.widget.Toolbar android: id="(a+id/too'lbar"

android:layout_width="match_parent"

android:layout_height="?attr/actionBarSize"

android:background="?attr/colorPrimary"

and roid: theme="(astyle/ThemeOverlay. AppCompat .Dark. ActionBar" app:popupTheme=,,(astyle/ThemeOverlay.AppCompat.Light" />

</FrameLayout>

ondroid.support design.widget.Navigationview

android:id="@+id/nav_view"

android:layout_width="match_parent"

android :layout__height="match_pa rent"

android:layout_gravity="start"

app:menu="@menu/nav_menu"

app:headerLayout="@layout/nav_header "/>

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

可以看到,我们将之前的TextView换成了 Navigation View,这样滑动菜单中显示的内容也就 变成NavigationView 了。这里又通过app:menu和app:headerLayout属性将我们刚才准备好的 menu和headerLayout设置了进去,这样NavigationView就定义完成了。

NavigationView虽然定义完成了,但是我们还要去处理菜单项的点击事件才行。修改 MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

private DrawerLayout mDrawerLayout;

(QOverride

protected void onCreate(Bundle savedlnstanceState) {

super.onCreate(savedlnstanceState);

setContentView(R.layout.activity main);

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

mDrawerLayout = (DrawerLayout) findViewById(R.id.drawerlayout);

NavigationView navView = (NavigationView) findViewByldiR.id.navview);

ActionBar actionBar = getSupportActionBar();

if (actionBar != null) {

actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeAsUpIndicator(R.drawable.icmenu);

} —

navView. setCheckedltem(R . id . nav_call);

navView.setNavigationltemSelectedListenerfnew NavigationViewOnNavigation ItemSelectedListener() {

©Override

public boolean onNavigationltemSelected(Menuitem item) { mDrawerLayout.closeDrawers(); return true;

});

代码还是比较简单的,这里首先获取到了 NavigationView的实例,然后调用它的 setCheckedltemO方法将Call菜单项设置为默认选中。接着调用了 setNavigat ionitem- SelectedListenerO方法来设置一个菜单项选中事件的监听器,当用户点击了任意菜单项时, 就会回调到onNavigationItemSelected()方法中。我们可以在这个方法中写相应的逻辑处理, 不过这里我并没有附加任何逻辑,只是调用了 DrawerLayout的closeDrawers ()方法将滑动菜单 关闭,这也是合情合理的做法。

现在可以重新运行一下程序了,点击一下Toolbar左侧的导航按钮,效果如图12.8所示。

怎么样?这样的滑动菜单页面,你无论如何也不能说它丑了吧? Material Design的魅力就在 这里,它真的是一种非常美观的设计理念,只要你按照它的各种规范和建议来设计界面,最终做 出来的程序就是特别好看的。

相信你对现在做出来的效果也一定十分满意吧?不过不要满足于现状,后面我们会实现更加 炫酷的效果。跟紧脚步,继续学习。

12.4悬浮按钮和可交互提示

        立面设计是Material Design中一条非常重要的设计思想,也就是说,按照Material Design的 理念,应用程序的界面不仅仅只是一个平面,而应该是有立体效果的。在官方给出的示例中,最 简单且最具代表性的立面设计就是悬浮按钮了,这种按钮不属于主界面平面的一部分,而是位于 另外一个维度的,因此就会给人一种悬浮的感觉。

        本节中我们会对这个悬浮按钮的效果进行学习,另外还会学习一种可交互式的提示工具。关 于提示工具,我们之前一直都是使用的Toast,但是Toast只能用于告知用户某某事情已经发生了, 用户却不能对此做出任何的响应,那么今天我们就将在这一方面进行扩展。

  1. FloatingActionButton

        FloatingActionButton是Design Support库中提供的一个控件,这个控件可以帮助我们比较轻 松地实现悬浮按钮的效果。其实在之前的图12.2中,我们就已经预览过悬浮按钮是长什么样子的 了,它默认会使用colorAccent来作为按钮的颜色,我们还可以通过给按钮指定一个图标来表明 这个按钮的作用是什么。

        下面开始来具体实现。首先仍然需要提前准备好一个图标,这里我放置了一张ic_done.png 到drawable-xxhdpi目录下。然后修改activity_main.xml中的代码,如下所示:

<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="(astyle/ThemeOverlay.AppCompat.Light" />

ondroid.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" />

</FrameLayout>

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

可以看到,这里我们在主屏幕布局中加入了一个FloatingActionButtono这个控件的用法并没 有什么特别的地方,layout width和layout height属性都指定成 wrap_content, layout gravity属性指定将这个控件放置于屏幕的右下角,其中end的工作原理和之前的 start是一样的,即如果系统语言是从左往右的,那么end就表示在右边,如果系统语言是从右 往左的,那么end就表示在左边。然后通过layout margin属性给控件的四周留点边距,紧贴 着屏幕边缘肯定是不好看的,最后通过src属性给FloatingActionButton设置了一个图标。

没错,就是这么简单,现在我们就可以来运行一下了,效果如图12.9所示。

一个漂亮的悬浮按钮就在屏幕的右下方出现了。

如果你仔细观察的话,会发现这个悬浮按钮的下面还有一点阴影。其实这很好理解,因为 FloatingActionButton是悬浮在当前界面上的,既然是悬浮,那么就理所应当会有投影,Design Support库连这种细节都帮我们考虑到了 。

说到悬浮,其实我们还可以指定FloatingActionButton的悬浮高度,如下所示:

<android.support.design.widget.FloatingActionButton

android: id="(a+icl/fab"

android:layout_width="wrap_content"

android: layoutheight^wrapcontent"

android:layoutg ravity="bottom|end"

android:layout_margin="16dp"

android: sr"c="@drawable/ic_done"

app:elevation=ll8dp" />

这里使用app:elevation属性来给FloatingActionButton指定一个高度值,高度值越大, 投影范围也越大,但是投影效果越淡,高度值越小,投影范围也越小,但是投影效果越浓。当 然这些效果的差异其实都不怎么明显,我个人感觉使用默认的FloatingActionButton效果就已经 足够了。

接下来我们看一下FloatingActionButton是如何处理点击事件的,毕竟,一个按钮首先要能 点击才有意义。修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

private DrawerLayout mDrawerLayout;

(aOverride

protected void onCreate(Bundle savedlnstanceState) { super,onCreate(savedlnstanceState); setContentView(R.layout.activitymain);

FloatingActionButton fab = (FloatingActionButton) findViewByld(R.id.fab); fab.setOnClickListener(new View.OnClickListener() {

^Override

public void onClick(View v) {

Toast.makeText(MainActivity.thisf "FAB clicked", Toast.LENGTH_ SHORT).show();

}

})

}

}

如果你在期待FloatingActionButton会有什么特殊用法的话,那可能就要让你失望了,它和 普通的Button其实没什么两样,都是调用setOnClickListener()方法来注册一个监听器,当点 击按钮时,就会执行监听器中的onClickf)方法,这里我们在onClickO方法中弹出了一个Toast。

现在重新运行一下程序,并点击FloatingActionButton,效果如图12.10所示。

  1. Snackbar

        现在我们已经掌握了 FloatingActionButton的基本用法,不过在上一小节处理点击事件的时 候,仍然是使用Toast来作为提示工具的,本小节中我们就来学习一个Design Support库提供的 更加先进的提示工具 Snackbar。

        首先要明确,Snackbar并不是Toast的替代品,它们两者之间有着不同的应用场景。Toast的 作用是告诉用户现在发生了什么事情,但同时用户只能被动接收这个事情,因为没有什么办法能 让用户进行选择。而Snackbar则在这方面进行了扩展,它允许在提示当中加入一个可交互按钮, 当用户点击按钮的时候可以执行一些额外的逻辑操作。打个比方,如果我们在执行删除操作的时 候只弹出一个Toast提示,那么用户要是误删了某个重要数据的话肯定会十分抓狂吧,但是如果 我们增加一个Undo按钮,就相当于给用户提供了一种弥补措施,从而大大降低了事故发生的概 率,提升了用户体验。

        Snackbar的用法也非常简单,它和Toast是基本相似的,只不过可以额外增加一个按钮的点 击事件。修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

private DrawerLayout mDrawerLayout;

(aOverride

protected void onCreate(Bundle savedlnstanceState) {

super.onCreate(savedlnstanceState);

setContentView(R.layout.activitymain);

FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListenerf) {

(aOverride

public void onClick(View view) {

Snackbar.make(view, "Data deleted", Snackbar.LENGTH_SHORT) .setActionfUndo", new View.OnClickListenerT) {

©Override public void onClick(View v) {

Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH^SHORT).show();

} _

})

.show()

}

});

}

}

可以看到,这里调用了 Snackbar的make()方法来创建一个Snackbar对象,make()方法的 第一个参数需要传入一个View,只要是当前界面布局的任意一个View都可以,Snackbar会使用 这个View来自动查找最外层的布局,用于展示Snackbaro第二个参数就是Snackbar中显示的内 容,第三个参数是Snackbar显示的时长。这些和Toast都是类似的。

接着这里又调用了一个setAction()方法来设置一个动作,从而让Snackbar不仅仅是一个 提示,而是可以和用户进行交互的。简单起见,我们在动作按钮的点击事件里面弹出一个Toast 提示。最后调用show()方法让Snackbar显示出来。

现在重新运行一下程序,并点击悬浮按钮,效果如图12.11所示。

可以看到,Snackbar从屏幕底部出现了,上面有我们所设置的提示文字,还有一个Undo按 钮,按钮是可以点击的。过一段时间后Snackbar会自动从屏幕底部消失。

不管是出现还是消失,Snackbar都是带有动画效果的,因此视觉体验也会比较好。

不过你有没有发现一个bug,这个Snackbar竟然将我们的悬浮按钮给遮挡住了。虽说也不是 什么重大的问题,因为Snackbar过一会儿就会自动消失,但这种用户体验总归是不友好的。有 没有什么办法能解决一下呢?当然有,只需要借助CoordinatorLayout就可以轻松解决。

  1. CoordinatorLayout

        CoordinatorLayout可以说是一个加强版的FrameLayout,这个布局也是由Design S叩port库 提供的。它在普通情况下的作用和FrameLayout基本一致,不过既然是Design Support库中提供 的布局,那么就必然有一些Material Design的魔力了。

        事实上,CoordinatorLayout可以监听其所有子控件的各种事件,然后自动帮助我们做出最为 合理的响应。举个简单的例子,刚才弹出的Snackbar提示将悬浮按钮遮挡住了,而如果我们能 让CoordinatorLayout监听到Snackbar的弹出事件,那么它会自动将内部的FloatingActionButton 向上偏移,从而确保不会被Snackbar遮挡到。

        至于CoordinatorLayout的使用也非常简单,我们只需要将原来的FrameLayout替换一下就可 以了。修改activity main.xml中的代码,如下所示:

<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/drawerlayout" android:layout_width="match_parent" android:layout_height="matchparent">

<android. support.design.widget.CoordinatorLayout

android:layout_width="match_parent"

android:layout_height="match_parent">

ondroid. 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="(astyle/ThemeOverlay.AppCompat.Light" />

ondroid.support.design.widget.FloatingActionButton

android:id="@+id/fab"

android: layout_width="wrap_content11 and「oid:layout_height="w「ap_content” android:layoutgravity="bottom|end" android:layout_margin="16dp" android:src=,,(adrawable/ic_done" />

</android support.design.widget.CoordinatorLayout>

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

由于CoordinatorLayout本身就是一个加强版的FrameLayout,因此这种替换不会有任何的副 作用。现在重新运行一下程序,并点击悬浮按钮,效果如图12.12所示。

可以看到,悬浮按钮自动向上偏移了 Snackbar的同等高度,从而确保不会被遮挡住,当 Snackbar消失的时候,悬浮按钮会自动向下偏移回到原来位置。

另外悬浮按钮的向上和向下偏移也是伴随着动画效果的,且和Snackbar完全同步,整体效 果看上去特别赏心悦目。

不过我们回过头来再思考一下,刚才说的是CoordinatorLayout可以监听其所有子控件的各种 事件,但是Snackbar好像并不是CoordinatorLayout的子控件吧,为什么它却可以被监听到呢?

其实道理很简单,还记得我们在Snackbar的make()方法中传入的第一个参数吗?这个参数 就是用来指定Snackbar是基于哪个View来触发的,刚才我们传入的是FloatingActionButton本身, 而FloatingActionButton是CoordinatorLayout中的子控件,因此这个事件就理所应当能被监听到 了。你可以自己再做个试验,如果给Snackbar的make()方法传入一个DrawerLayout,那么Snackbar 就会再次遮挡住悬浮按钮,因为DrawerLayout不是CoordinatorLayout的子控件,CoordinatorLayout 也就无法监听到Snackbar的弹出和隐藏事件了。

本节的内容就到这里,接下来我们继续丰富MaterialTest项目,加入卡片式布局效果。

12.5卡片式布局

        虽然现在MaterialTest中已经应用了非常多的Material Design效果,不过你会发现,界面上 最主要的一块区域还处于空白状态。这块区域通常都是用来放置应用的主体内容的,我准备使用 一些精美的水果图片来填充这部分区域。

        那么为了要让水果图片也能Material化,本节中我们将会学习如何实现卡片式布局的效果。

        卡片式布局也是Materials Design中提岀的一个新的概念,它可以让页面中的元素看起来就像在 卡片中一样,并且还能拥有圆角和投影,下面我们就开始具体学习一下。

  1. CardView

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

        我们先来看一下CardView的基本用法吧,其实非常简单,如下所示:

<android.support.v7.widget.CardView

android:layoutwidth="matchparent"

android:layout_height="wrap_content"

app:cardCornerRadius="4dp" app:elevation="5dp"> <TextView

android: id="(a+id/info_text"

android:layout_width="match_parent" android:layout_height="wrap_content,7>

</android.support.v7.widget.CardView>

        这里定义了一个CardView布局,我们可以通过app: cardCornerRadius属性指定卡片圆角 的弧度,数值越大,圆角的弧度也越大。另外还可以通过app:elevation属性指定卡片的高度, 高度值越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围也越小,但是投影效 果越浓,这一点和FloatingActionButton是一致的。

        然后我们在CardView布局中放置了一个TextView,那么这个TextView就会显示在一张卡片 当中了,CardView的用法就是这么简单。

        但是我们显然不可能在如此宽阔的一块空白区域内只放置一张卡片,为了能够充分利用屏幕 的空间,这里我准备综合运用一下第3章中学到的知识,使用RecyclerView来填充MaterialTest 项目的主界面部分。还记得之前实现过的水果列表效果吗?这次我们将升级一下,实现一个高配 版的水果列表效果。

        既然是要实现水果列表,那么首先肯定需要准备许多张水果图片,这里我从网上挑选了一些 精美的水果图片,将它们复制到了项目当中。

        然后由于我们还需要用到RecyclerView CardView这几个控件,因此必须在app/build.gradle 文件中声明这些库的依赖才行:

dependencies {

compile fileTree(dir: 1 libs 1, include: ['*.jar1])

compile 'com.android.support:appcompat-v7:24.2.1'

testCompile 'junit:junit:4.12'

compile 1 com.android.support:design:24.2.1'

compile 'de.hdodenhof:circleimageview:2.1.0'

compile 'com.android.support:recyclerview-v7:24.2.1'

compile 'com.android.support:cardview-v7:24.2.1*

compile 'com.githubbumptech.glide:glide:3.7.0'

}

注意上述声明的最后一行,这里添加了一个Glide库的依赖。Glide是一个超级强大的图片加 载库,它不仅可以用于加载本地图片,还可以加载网络图片、GIF图片、甚至是本地视频。最重 要的是,Glide的用法非常简单,只需一行代码就能轻松实现复杂的图片加载功能,因此这里我 们准备用它来加载水果图片。Glide的项目主页地址是:https://github.com/bumptech/glideo

接下来开始具体的代码实现,修改activity main.xml 的代码,如下所示:

<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="(a4-id/drawe rlayout" 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="matchparent"

android:layout_height="?attr/actionBarSize"

android:background="?attr/colorPrimary"

android :theme="(astyle/ThemeOverlay.AppCompat .Dark. ActionBar" app:popupTheme="(astyle/ThemeOverlay.AppCompat.Light" />

<android.support v7 widget.RecyclerView

android:id="@+id/recycle r_view"

and roid :layout_width=,,match_parent,, android:layout_height="match_parent" />

<android.support.design.widget.FloatingActionButton

android: id="(a+id/fab"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="bottomI end" android:layoutjnargin="16dp" android:src="@drawable/ic_done" />

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

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

这里我们在CoordinatorLayout中添加了一个RecyclerView,给它指定一个id,然后将宽度和 高度都设置为match__parent,这样RecyclerView也就占满了整个布局的空间。

接着定义一个实体类Fruit,代码如下所示:

public class Fruit {

private String name;

private int imageld;

public Fruit(String name, int imageld) {

this.name = name;

this.imageld = imageld;

}

public String getName() {

return name;

}

public int getlmageld() {

return imageld;

}

}

Fruit类中只有两个字段,name表示水果的名字,imageld表示水果对应图片的资源id。 然后需要为RecyclerView的子项指定一个我们自定义的布局,在layout目录下新建fruit_item. xml,代码如下所示:

<android.support.v7.widget.CardView

xmlns:android="http://schemas.android.com/apk/res/and roid"

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:orientation="vertical"

android: layout_width=,lmatch_parent"

android:layout_height="wrap_content">

<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=l,wrap_content" android: layout_height=,,wrap_content" android:layoutgravity="center_horizontal" android:layout_margin="5dp" and roid:textSize="16sp" />

</LinearLayout>

</android.support.v7.widget.CardView>

这里使用了 CardView来作为子项的最外层布局,从而使得RecyclerView中的每个元素都是 在卡片当中的。CardView由于是一个FrameLayout,因此它没有什么方便的定位方式,这里我们 只好在CardView中再嵌套一个LinearLayout,然后在LinearLayout中放置具体的内容。

内容倒也没有什么特殊的地方,就是定义了一个ImageView用于显示水果的图片,又定义了 一个TextView用于显示水果的名称,并让TextView在水平方向上居中显示。注意在Image View 中我们使用了一个scaleType属性,这个属性可以指定图片的缩放模式。由于各张水果图片的 长宽比例可能都不一致,为了让所有的图片都能填充满整个Image View,这里使用了 centerCrop 模式,它可以让图片保持原有比例填充满ImageView,并将超出屏幕的部分裁剪掉。

接下来需要为RecyclerView准备一个适配器,新建FruitAdapter类,让这个适配器继承自 RecyclerView.Adapter,并将泛型指定为 FruitAdapter.ViewHolder,代码如下所示:

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> (

private Context mContext;

private List<Fruit> mFruitList;

static class ViewHolder extends RecyclerView.ViewHolder {

CardView cardView;

ImageView fruitimage; TextView fruitName;

public ViewHolder(View view) { super(view);

cardView = (CardView) view; fruitimage = (ImageView) view.findViewByld(R.id.fruit image); fruitName = (TextView) view.findViewById(R.id.fruitname);

}

}

public FruitAdapter(List<Fruit> fruitList) { mFruitList = fruitList;

}

(aOverride

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (mContext == null) {

mContext = parent.getContext();

}

View view = LayoutInflater.from(mContext).inflate(R.layout.fruititem, parent, false);

return new ViewHolder(view);;

)

^Override

public void onBindViewHolder(ViewHolder holder, int position) {

Fruit fruit = mFruitList.get(position);

holder.fruitName.setText(fruit.getName());

Glide.with(mContext).load(f ruit.getlmageld()).into(holder.fruitimage);

©Override

public int getltemCount() {

return mFruitList.size();

}

}

上述代码相信你一定很熟悉,和我们在第3章中编写的FruitAdapter几乎一模一样。唯一需 要注意的是,在onBindViewHolderO方法中我们使用了 Glide来加载水果图片。

那么这里就顺便来看一下Glide的用法吧,其实并没有太多好讲的,因为Glide的用法实在 是太简单了。首先调用Glide.with()方法并传入一个Context. Activity或Fragment参数, 然后调用load()方法去加载图片,可以是一个URL地址,也可以是一个本地路径,或者是一个 资源id,最后调用into()方法将图片设置到具体某一个ImageView中就可以了。

那么我们为什么要使用Glide而不是传统的设置图片方式呢?因为这次我从网上找的这些水 果图片像素都非常高,如果不进行压缩就直接展示的话,很容易就会引起内存溢出。而使用Glide 就完全不需要担心这回事,因为Glide在内部做了许多非常复杂的逻辑操作,其中就包括了图片 压缩,我们只需要安心按照Glide的标准用法去加载图片就可以了。

这样我们就将RecyclerView的适配器也准备好了,最后修改MainActivity中的代码,如下 所示:

public class MainActivity extends AppCompatActivity {

private DrawerLayout mDrawerLayout;

private Fruit[] fruits = {new Fruit ("Apple", R. d rawable. apple), new Fruit ("Banana", R.drawable.banana),

new Fruit("Orange", R.drawable.orange), new Fruit("Watermelon", R. drawable.watermelon)r

new Fruit ("Pear", R. drawable, pear) y new FruitCGrape" f R. drawable, grape),

new Fruit("Pineapple", R.drawable.pineapple)r new Fruit("Strawberry", R.drawable. strawberry),

new Fruit("Cherry", R.drawable.cherry), new Fruit("Mango", R.drawable. mango)};

private List<Fruit> fruitList = new ArrayList<>();

private FruitAdapter adapter;

©Override protected void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.activitymain);

initFruits();

RecyclerView recyclerView = (RecyclerView) findViewById(R.id. recycler_view);

GridLayoutManager layoutManager = new GridLayoutManager(thisf 2); recyclerView.setLayoutManager(layoutManager);

adapter = new FruitAdapter(fruitList); recyclerView.setAdapter(adapter);

private void initFruitsO {

fruitList.clear();

for (int i = 0; i < 50; i++) { Random random = new Random(); int index = random.nextlnt(fruits.length); fruitList.add(fruits[index]);

}

在MainActivity中我们首先定义了一个数组,数组里面存放了很多个Fruit的实例,每个实 例都代表着一种水果。然后在initFruitsO方法中,先是清空了一下f ruitList中的数据,接 着使用一个随机函数,从刚才定义的Fruit数组中随机挑选一个水果放入到fruitList当中, 这样每次打开程序看到的水果数据都会是不同的。另外,为了让界面上的数据多一些,这里使用 了一个循环,随机挑选50个水果。

之后的用法就是RecyclerView的标准用法了,不过这里使用了 GridLayoutManager这种布局 方式。在第 3 章中我们已经学过了 LinearLayoutManager 和 StaggeredGridLayoutManager,现在终 于将所有的布局方式都补齐了。GridLayoutManager的用法也没有什么特别之处,它的构造函数 接收两个参数,第一个是Context,第二个是列数,这里我们希望每一行中会有两列数据。

现在重新运行一下程序,效果如图12.13所示。

可以看到,精美的水果图片成功展示出来了。每个水果都是在一张单独的卡片当中的,并且 还拥有圆角和投影,是不是非常美观?另外,由于我们是使用随机的方式来获取水果数据的,因 此界面上会有一些重复的水果出现,这属于正常现象。

当你陶醉于当前精美的界面的时候,你是不是忽略了一个细节?哎呀,我们的Toolbar怎么 不见了!仔细观察一下原来是被RecyclerView给挡住了。这个问题又该怎么解决呢?这就需要借 助到另外一个工具了 AppBarLayouto

  1. AppBarLayout

        首先我们来分析一下为什么RecyclerView会把Toolbar给遮挡住吧。其实并不难理解,由于 RecyclerView 和 Toolbar都是放置在 CoordinatorLayout 中的,而前面已经说过,CoordinatorLayout 就是一个加强版的FrameLayout,那么FrameLayout中的所有控件在不进行明确定位的情况下, 默认都会摆放在布局的左上角,从而也就产生了遮挡的现象。其实这已经不是你第一次遇到这种 情况了,我们在3.3.3小节学习FrameLayout的时候就早已见识过了控件与控件之间遮挡的效果。

        既然已经找到了问题的原因,那么该如何解决呢?传统情况下,使用偏移是唯一的解决办法, 即让RecyclerView向下偏移一个Toolbar的高度,从而保证不会遮挡到Toolbar。不过我们使用的 并不是普通的FrameLayout,而是CoordinatorLayout,因此自然会有一些更加巧妙的解决办法。

        这里我准备使用Design Support库中提供的另外一个工具 AppBarLayouto AppBarLayout 实际上是一个垂直方向的LinearLayout,它在内部做了很多滚动事件的封装,并应用了一些 Material Design的设计理念。

        那么我们怎样使用AppBarLayout才能解决前面的覆盖问题呢?其实只需要两步就可以了, 第一步将Toolbar嵌套到AppBarLayout中,第二步给RecyclerView指定一个布局行为。修改 activity_main.xml中的代码,如下所示:

<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="(a+id/d rawe rlayout" android: ■Layout_width="match_parent" android :layout_height=,,match_parent">

<android.support.design.widget.CoordinatorLayout

android:layout_width="match_pa rent"

android:layout_height="match_parent">

ondroid.support.design.widget.AppBarLayout

android: layout_width=,,match_parent" android :layout_height=,,wrap_content,,>

ondroid. 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="(astyle/ThemeOverlay. AppCompat. Light" />

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

<android.support.v7.widget.RecyclerView

android:id="@+id/recyclerview"

android:layout_width="matchparent" android:layout_height="match_parent" app:layout_behavior=,,@string7appbar_scroHing_view_behavior" />

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

</android.support.v4.widget.Drawe「Layout〉

可以看到,布局文件并没有什么太大的变化。我们首先定义了一个AppBarLayout,并将 Toolbar放置在了 AppBarLayout里面,然后在 RecyclerView 中使用 app: layout behavior 属性 指定了一个布局行为。其中appbar scrolling view behavior这个字符串也是由Design Support库提供的。

现在重新运行一下程序,你就会发现一切都正常了,如图12.14所示。

虽说使用AppBarLayout已经成功解决了 RecyclerView遮挡Toolbar的问题,但是刚才有提到 过,说AppBarLayout中应用了一些Material Design的设计理念,好像从上面的例子完全体现不 出来呀。事实上,当RecyclerView滚动的时候就已经将滚动事件都通知给AppBarLayout 了,只 是我们还没进行处理而已。那么下面就让我们来进一步优化,看看AppBarLayout到底能实现什么样的Material Design效果。

当AppBarLayout接收到滚动事件的时候,它内部的子控件其实是可以指定如何去影响这些 事件的,通过app:layout scrollFlags属性就能实现。修改activity main.xml中的代码,如 下所示:

<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/drawerlayout" android: layout_width=,,match_parent" android: layout_height=,,match_parent">

ondroid.support.design.widget.CoordinatorLayout

android:layout_width="matchparent"

android: layout_height=,lmatch_parent">

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

ondroid. support .v7 .widget .Toolbar

android:id="@+id/toolbar"

android:layout_width="match_pa rent"

android:layout_height="?attr/actionBarSize"

android:background="?attr/colorPrimary"

and roid: theme="(astyle/ThemeOve rlay. AppCompat .Dark. ActionBar" app: popupTheme="(astyle/ThemeOverlay. AppCompat. Light" app:layout_scrollFlags=,,scroll|enterAlways| snap" />

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

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

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

这里在Toolbar中添加了一个app: layout scrollFlags属性,并将这个属性的值指定成了 scroll | enterAlways | snapo 其中,scroll 表示当 RecyclerView 向上滚动的时候,Toolbar 会 跟着一起向上滚动并实现隐藏;enterAlways表示当RecyclerView向下滚动的时候,Toolbar会 跟着一起向下滚动并重新显示。snap表示当Toolbar还没有完全隐藏或显示的时候,会根据当前 滚动的距离,自动选择是隐藏还是显示。

我们要改动的就只有这一行代码而已,现在重新运行一下程序,并向上滚动RecyclerView, 效果如图12.15所示。

可以看到,随着我们向上滚动RecyclerView, Toolbar竟然消失了,而向下滚动RecyclerView, Toolbar又会重新出现。这其实也是Material Design中的一项重要设计思想,因为当用户在向上 滚动RecyclerView的时候,其注意力肯定是在RecyclerView的内容上面的,这个时候如果Toolbar 还占据着屏幕空间,就会在一定程度上影响用户的阅读体验,而将Toolbar隐藏则可以让阅读体 验达到最佳状态。当用户需要操作Toolbar ±的功能时,只需要轻微向下滚动,Toolbar就会重新 出现。这种设计方式,既保证了用户的最佳阅读效果,又不影响任何功能上的操作,MaterialDesign 考虑得就是这么细致入微。

当然了,像这种功能,如果是使用ActionBar的话,那就完全不可能实现了,Toolbar的出现 为我们提供了更多的可能。

12.6下拉刷新

        下拉刷新这种功能早就不是什么新鲜的东西了,几乎所有的应用里都会有这个功能。不过市 面上现有的下拉刷新功能在风格上都各不相同,并且和Material Design还有些格格不入的感觉。 因此,谷歌为了让Android的下拉刷新风格能有一个统一的标准,于是在Material Design中制定 了一个官方的设计规范。当然,我们并不需要去深入了解这个规范到底是什么样的,因为谷歌早 就提供好了现成的控件,我们只需要在项目中直接使用就可以了。

        SwipeRefreshLayout就是用于实现下拉刷新功能的核心类,它是由support・v4库提供的。我 们把想要实现下拉刷新功能的控件放置到SwipeRefireshLayout中,就可以迅速让这个控件支持下 拉刷新。那么在MaterialTest项目中,应该支持下拉刷新功能的控件自然就是RecyclerView 了。

        由于SwipeRefreshLayout的用法也比较简单,下面我们就直接开始使用了。修改activity_

main.xml中的代码,如下所示:

<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/drawerlayout"

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/recycler_view"

android: layout_width=,,match_parent"

android:layout_height="match_parent" />

</android support.v4 widget.SwipeRefreshLayout>

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

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

可以看到,这里我们在RecyclerView的外面又嵌套了一层SwipeRefreshLayout,这样 RecyclerView就自动拥有下拉刷新功能了。另外需要注意,由于RecyclerView现在变成了 Swipe- RefreshLayout的子控件,因此之前使用app:layout behavior声明的布局行为现在也要移到 SwipeRefreshLayout 中才行。

不过这还没有结束,虽然RecyclerView已经支持下拉刷新功能了,但是我们还要在代码中处 理具体的刷新逻辑才行。修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

private SwipeRefreshLayout swipeRefresh;

(QOverride

protected void onCreate(Bundle savedinstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.activitymain); swipeRefresh = (SwipeRefreshLayout) findViewByld(R.id.swipe_refresh); swipeRefresh.setColorSchemeResources(R.color.colorPrimary); swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.

OnRefreshListener() {

@0verride

public void onRefresh() { refreshFruitsf);

}

})

}

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() { initFruitsO; adapter.notifyDataSetChanged(); swipeRefresh.setRefreshing(false);

}

})

}

}).start();

}

}

这段代码应该还是比较好理解的,首先通过findViewById()方法拿到SwipeRefreshLayout 的实例,然后调用setColorSchemeResources()方法来设置下拉刷新进度条的颜色,这里我们 就使用主题中的colorPrimary作为进度条的颜色了。接着调用setOnRef reshListener()方法来 设置一个下拉刷新的监听器,当触发了下拉刷新操作的时候就会回调这个监听器的0nRefreshO 方法,然后我们在这里去处理具体的刷新逻辑就可以了。

通常情况下,onRef「esh()方法中应该是去网络上请求最新的数据,然后再将这些数据展示 出来。这里简单起见,我们就不和网络进行交互了,而是调用一个refreshFruitsO方法进行 本地刷新操作。refreshFruitsO方法中先是开启了一个线程,然后将线程沉睡两秒钟。之所以 这么做,是因为本地刷新操作速度非常快,如果不将线程沉睡的话,刷新立刻就结束了,从而看 不到刷新的过程。沉睡结束之后,这里使用了 runOnUiThread()方法将线程切换回主线程,然后 调用initFruitsO方法重新生成数据,接着再调用FruitAdapter的notifyDataSetChanged() 方法通知数据发生了变化,最后调用SwipeRefreshLayout的setRefreshing()方法并传入 false,用于表示刷新事件结束,并隐藏刷新进度条。

现在可以重新运行一下程序了,在屏幕的主界面向下拖动,会有一个下拉刷新的进度条出现, 松手后就会自动进行刷新了,效果如图12.16所示。

下拉刷新的进度条只会停留两秒钟,之后就会自动消失,界面上的水果数据也会随之更新。

这样我们就把下拉刷新的功能也成功实现了,并且这就是Material Design中规定的最标准的 下拉刷新效果,还有什么会比这个更好看呢?目前我们的项目中已经应用了众多Material Design 的效果,Design Support库中的常用控件也学了大半了。不过本章的学习之旅还没有结束,在最 后的尾声部分,我们再来实现一个非常震撼的Material Design效果——可折叠式标题栏。

12.7可折叠式标题栏

        虽说我们现在的标题栏是使用Toolbar来编写的,不过它看上去和传统的ActionBar其实没 什么两样,只不过可以响应RecyclerView的滚动事件来进行隐藏和显示。而Material Design中 并没有限定标题栏必须是长这个样子的,事实上,我们可以根据自己的喜好随意定制标题栏的 样式。那么本节中我们就来实现一个可折叠式标题栏的效果,需要借助CollapsingToolbarLayout 这个工具。

  1. CollapsingToolbarLayout

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

        不过,CollapsingToolbarLayout是不能独立存在的,它在设计的时候就被限定只能作为 AppBarLayout的直接子布局来使用。而AppBarLayout又必须是CoordinatorLayout的子布局,因 此本节中我们要实现的功能其实需要综合运用前面所学的各种知识。那么话不多说,这就开始吧。

        首先我们需要一个额外的活动来作为水果的详情展示界面,右击com.example.materialtest包 ->New-^Activity-^Empty Activity,创建一个 FruitActivity,并将布局名指定成 activity_fruit.xml, 然后我们开始编写水果详情展示界面的布局。

        由于整个布局文件比较复杂,这里我准备采用分段编写的方式。activity fruit.xml中的内容 主要分为两部分,一个是水果标题栏,一个是水果内容详情,我们来一步步实现。

        首先实现标题栏部分,这里使用CoordinatorLayout来作为最外层布局,如下所示:

<android.support.design.widget.CoordinatorLayout

xmlns:android="http://schemas.android.com/apk/res/and roid"

xmlns:app="http://schemas.android.com/apk/res-auto" android:layoutwidth="matchparent"

android:layout_height="match_parent">

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

        一开始的代码还是比较简单的,相信没有什么需要解释的地方。注意始终记得要定义一个 xmlns : app的命名空间,在Material Design的开发中会经常用到它。

接着我们在CoordinatorLayout中嵌套一个AppBarLayout,如下所示:

<android.support.design.widget.CoordinatorLayout

xmlns:android="http://schemas.android.com/apk/res/and roid"

xmlns:app="http://schemas.android.com/apk/res-auto" 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.AppBa rLayout>

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

目前为止也没有什么难理解的地方,我们给AppBarLayout定义了一个id,将它的宽度指定

* match_parent,高度指定为250dp。当然这里的高度值你可以随意指定,不过我尝试之后发 现250dp的视觉效果比较好。

接下来我们在AppBarLayout中再嵌套一个CollapsingToolbarLayout,如下所示:

<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_pareni:"

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" and roid: ,Layout_height=,,match_pa rent" android: theme="@style/ThemeOver'lay. AppCompat.Dark. ActionBar11 app:contentScrim="?attr/colorPrimary" app:layout_sc rollFlags="sc roll|exitUntilCollapsed"> </android.support.design.widget.CollapsingToolbarLayout>

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

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

从现在开始就稍微有点难理解了,这里我们使用了新的布局CollapsingToolbarLayouto其中, id、layout width和layout_height这几个属性比较简单,我就不解释了。android:theme 属性指定了一个ThemeOverlay.AppCompat.Dark.ActionBar的主题,其实对于这部分我们也并不 陌生,因为之前在activity main.xmI中给Toolbar指定的也是这个主题,只不过这里要实现 更加高级的Toolbar效果,因此需要将这个主题的指定提到上一层来。app:contentScrim属性 用于指定CollapsingToolbarLayout在趋于折叠状态以及折叠之后的背景色,其实Collapsing- ToolbarLayout在折叠之后就是一个普通的Toolbar,那么背景色肯定应该是colorPrimary 了,具 体的效果我们待会儿就能看到。app:layout scrollFlags属性我们也是见过的,只不过之前 是给Toolbar指定的,现在也移到外面来了。其中,scroll表示CollapsingToolbarLayout会随着水 果内容详情的滚动一起滚动,exitUntilCollapsed表示当CollapsingToolbarLayout随着滚动完 成折叠之后就保留在界面上,不再移出屏幕。

接下来,我们在CollapsingToolbarLayout中定义标题栏的具体内容,如下所示:

<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_pa rent" android:layout_height="match_pa rent">

ondroid.support.design.widget.AppBarLayout

android:id="@+id/appBar"

android:layout_width="match_parent"

android:layout_height="250dp">

ondroid.support.design.widget.CollapsingToolbarLayout

android :id=,,@+id/collapsing_toolbar"

android:layout_width="matchparent" android:layout_height="matchparent" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:contentScrim="?attr/colorPrimary" app:layout_scrollF'lags="scroH | 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>

可以看到,我们在CollapsingToolbarLayout中定义了一个ImageView和一个Toolbar,也就意 味着,这个高级版的标题栏将是由普通的标题栏加上图片组合而成的。这里定义的大多数属性我 们都是见过的,就不再解释了,只有一个app: layout collapseMode比较陌生。它用于指定当 前控件在CollapsingToolbarLayout折叠过程中的折叠模式,其中Toolbar指定成pin,表示在折叠 的过程中位置始终保持不变,ImageView指定成parallax,表示会在折叠的过程中产生一定的错 位偏移,这种模式的视觉效果会非常好。

这样我们就将水果标题栏的界面编写完成了,下面开始编写水果内容详情部分。继续修改 activity_fruit.xml中的代码,如下所示:

<android.support.design.widget.CoordinatorLayout

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto" android:layoutwidth="matchparent" 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_scroHing_view__behavior">

</android support.v4.widget.NestedScrollView>

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

水果内容详情的最外层布局使用了一个NestedScrollView,注意它和AppBarLayout是平级的。 我们之前在9.2.1小节学过ScrollView的用法,它允许使用滚动的方式来查看屏幕以外的数据, 而NestedScrollView在此基础之上还增加了嵌套响应滚动事件的功能。由于CoordinatorLayout本 身已经可以响应滚动事件了,因此我们在它的内部就需要使用NestedScrollView或RecyclerView 这样的布局。另外,这里还通过app: layout behavior属性指定了一个布局行为,这和之前在 RecyclerView中的用法是一模一样的。

不管是ScrollView还是NestedScrollView,它们的内部都只允许存在一个直接子布局。因此, 如果我们想要在里面放入很多东西的话,通常都会先嵌套一个LinearLayout,然后再在 LinearLayout中放入具体的内容就可以了,如下所示:

<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="matchparent"

android:layout_height="match_parent">

<android.support.v4.widget.NestedScrollView

android: layout_width=,,match_parent" android:layout_height="match_parent" app: layout_behavior=,l@string7appbar_scrolling_view_behavior">

<LinearLayout

android:orientation="vertical"

android:layout_width="match_parent" android:layout_height="wrap_content"> </LinearLayout>

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

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

这里我们嵌套了一个垂直方向的LinearLayout,并将layoutwidth设置为match parent, 将 layout height 设置为 wrap content。接下来在LinearLayout中放入具体的内容,这里我准备使用一个TextView来显示水果的内 容详情,并将TextView放在一个卡片式布局当中,如下所示:

<android.support.design.widget.CoordinatorLayout

xmlns:and roid="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.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="matchparent" android:orientation="vertical">

<android.support v7.widget.Cardview

android: ■Layout_width="match_parent" android: ■Layout__height="wrap_content" android:layout_ma rginBottom="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.CoordinatorLayout>

这段代码也没有什么难理解的地方,都是我们学过的知识。需要注意的是,这里为了让界面 更加美观,我在CardView和TextView上都加了一些边距。其中,CardView的marginTop加了 35dp 的边距,这是为下面要编写的东西留岀空间。

好的,这样就把水果标题栏和水果内容详情的界面都编写完了,不过我们还可以在界面上再 添加一个悬浮按钮。这个悬浮按钮并不是必需的,根据具体的需求添加就可以了,如果加入的话, 我们将免费获得一些额外的动画效果。

为了做出示范,我就准备在activity fruit.xm 1中加入一个悬浮按钮了。这个界面是一个水果 详情展示界面,那么我就加入一个表示评论作用的悬浮按钮吧。首先需要提前准备好一个图标, 这里我放置了一张ic_comment.piig到drawable-xxhdpi目录下。然后修改activity fruit.xml中的代 码,如下所示:

<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.support.design.widget.AppBarLayout

android: id="(a4-id/appBar"

android:layout_width="match_parent"

android:layout_height="250dp">

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

<android.support.v4.widget.NestedScrollView

android:layoutwidth="matchparent"

android:layout_height="matchparent"

app: layout_behavior="(astring7appbar_scrolling_view_behavior">

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

<android support.design.widget.FloatingActionButton

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android :layout_margin=ll16dpl>

android: src="@dr"awable/ic_comment”

app: ■Layout_anchor="@id/appBar"

app:layout_anchorGravity="bottom|end" />

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

可以看到,这里加入了一个 FloatingActionButton,它和 AppBarLayout 以及 NestedScrollView 是平级的。FloatingActionButton中使用app: layout anchor属性指定了一个锚点,我们将锚点 设置为AppBarLayout,这样悬浮按钮就会出现在水果标题栏的区域内,接着又使用app:layout_ anchorGravity属性将悬浮按钮定位在标题栏区域的右下角。其他一些属性都比较简单,就不 再进行解释了。

好了,现在我们终于将整个activity fruit.xm 1布局都编写完了,内容虽然比较长,但由于是 分段编写的,并且每一步我都进行了详细的说明,相信你应该看得很明白吧。

界面完成了之后,接下来我们开始编写功能逻辑,修改FruitActivity中的代码,如下所示:

public class FruitActivity extends AppCompatActivity (

public static final String FRUITNAME = "fruitname";

public static final String FRUITIMAGEID = "fruit_image_id";

^Override

protected void onCreate(Bundle savedlnstanceState) {

super.onCreate(savedlnstanceState); setContentView(R.layout.activityfruit); Intent intent = getlntent();

String fruitName = intent.getStringExtra(FRUITNAME);

int fruitlmageld = intent.getlntExtra(FRUIT_IMAGE_IDz 0); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsingtoolbar);

ImageView fruitlmageView = (ImageView) findViewByld(R.id.fruitimageview); Textview fruitContentText = (Textview) fIndViewByld(R.id.fruit_content_ text);

setSupportActionBar(toolbar);

ActionBar actionBar = getSupportActionBar();

if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true);

}

collapsingToolbar.setTitle(fruitName);

Glide.with(this).load(fruitImageld).into(fruitImageView);

String fruitContent = generateFruitContent(fruitName); fruitContentText.setText(fruitContent);

}

private String generateFruitContent(String fruitName) ( StringBuilder fruitContent = new StringBuilder(); for (int i = 0; i < 500; i++) {

fruitContent.append(fruitName);

}

return fruitContent.toString();

}

(aOverride

public boolean onOptionsItemSelected(Menuitem item) {

switch (item.getltemld()) {

case android.R.id.home:

finishf); return true;

}

return super.onOptionsItemSelected(item);

}

}

FruitActivity中的代码并不是很复杂。首先,在onCreateO方法中,我们通过Intent获取到 传入的水果名和水果图片的资源id,然后通过findViewByldO方法拿到刚才在布局文件中定义 的各个控件的实例。接着就是使用了 Toolbar的标准用法,将它作为ActionBar显示,并启用 HomeAsUp按钮。由于HomeAsUp按钮的默认图标就是一个返回箭头,这正是我们所期望的, 因此就不用再额外设置别的图标了。

接下来开始填充界面上的内容,调用CollapsingToolbarLayout的setTitle()方法将水果名 设置成当前界面的标题,然后使用Glide加载传入的水果图片,并设置到标题栏的ImageView± 面。接着需要填充水果的内容详情,由于这只是一个示例程序,并不需要什么真实的数据,所以 我使用了一个generateFruitContent ()方法将水果名循环拼接500次,从而生成了一个比较长 的字符串,将它设置到了 TextView上面。

最后,我们在onOptionsItemSelected ()方法中处理了 HomeAsUp按钮的点击事件,当点 击了这个按钮时,就调用finishO方法关闭当前的活动,从而返回上一个活动。

所有工作都完成了吗?其实还差最关键的一步,就是处理RecyclerView的点击事件,不然的 话我们根本就无法打开FruitActivity。修改FruitAdaptei•中的代码,如下所示:

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> (

@Override

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (mContext == null) {

mContext = parent.getContext();

View view = Layoutlnflater.from(mContext).inflate(R.layout.fruititem, 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.getlmageld()); mContext sta rtActivity(intent);

}

})

return holder;

最关键的一步其实也是最简单的,这里我们给CardView注册了一个点击事件监听器,然后 在点击事件中获取当前点击项的水果名和水果图片资源id,把它们传入到Intent中,最后调用 startActivity()方法启动 Fruit Activity o

见证奇迹的时刻到了,现在重新运行一下程序,并点击界面上的任意一个水果,比如我点击 了葡萄,效果如图12.17所示。

你没有看错,如此精美的界面就是我们亲手敲出来的。这个界面上的内容分为三部分,水果 标题栏、水果内容详情和悬浮按钮,相信你一眼就能将它们区分出来吧。Toolbar和水果背景图 完美地融合到了一起,既保证了图片的展示空间,又不影响Toolbar的任何功能,那个向左的箭头就是用来返回上一个活动的。

不过这并不是全部,真正的好戏还在后头。我们尝试向上拖动水果内容详情,你会发现水果 背景图上的标题会慢慢缩小,并且背景图会产生一些错位偏移的效果,如图12.18所示。

这是由于用户想要查看水果的内容详情,此时界面的重点在具体的内容上面,因此标题栏就 会自动进行折叠,从而节省屏幕空间。

继续向上拖动,直到标题栏变成完全折叠状态,效果如图12.19所示。

可以看到,标题栏的背景图片不见了,悬浮按钮也自动消失了,现在水果标题栏变成了一个 最普通的Toolbar0这是由于用户正在阅读具体的内容,我们需要给他们提供最充分的阅读空间。 而如果这个时候向下拖动水果内容详情,就会执行一个完全相反的动画过程,最终恢复成图12.17 的界面效果。

不知道你有没有被这个效果所感动呢?在这里,我真心地感谢Material Design送给我们的 礼物。

12.7.2充分利用系统状态栏空间

        虽说现在水果详情展示界面的效果已经非常华丽了,但这并不代表我们不能再进一步地提 升。观察一下图12.17,你会发现水果的背景图片和系统的状态栏总有一些不搭的感觉,如果我 们能将背景图和状态栏融合到一起,那这个视觉体验绝对能提升好几个档次。

        只不过很可惜的是,在Android 5.0系统之前,我们是无法对状态栏的背景或颜色进行操作 的,那个时候也还没有Material Design的概念。但是Android 5.0及之后的系统都是支持这个功 能的,因此这里我们就来实现一个系统差异型的效果,在Android 5.0及之后的系统中,使用背 景图和状态栏融合的模式,在之前的系统中使用普通的模式。

        想要让背景图能够和系统状态栏融合,需要借助android: fitsSystemWindows这个属性来 实现。在 CoordinatorLayoutA AppBarLayout、CollapsingToolbarLayout 这种嵌套结构的布局中, 将控件的android:fitsSystemWindows属性指定成true,就表示该控件会出现在系统状态栏 里。对应到我们的程序,那就是水果标题栏中的ImageView应该设置这个属性了。不过只给 ImageView设置这个属性是没有用的,我们必须将ImageView布局结构中的所有父布局都设置上 这个属性才可以,修改activity_fruit.xml中的代码,如下所示:

<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="matchpa rent"

android:layout_height="250dp"

android: fitsSystemWindows="true,,>

ondroid.support.design.widget.CollapsingToolbarLayout

android:id="@+id/collapsing toolbar"

and roid: layout_width=,,match_pa rent"

android:layout_height="match_parent"

and roid:theme="@style/ThemeOve rlay.AppCompat.Dark.ActionBar"

android: fitsSystemWindows=,,true"

app:contentScrim="?attr/colorPrimary"

app:layout scrollFlags="sc roll|exitUntilCollapsed">

<ImageView

and roid: id="(a+id/f ruit_image_view"

android:layout_width="match_parent"

android: layout_height=,,match_parent"

android:scaleType="centerCrop" android: fitsSystemWindows=,,true" app:layout collapseModeZparallax" />

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

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

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

但是,即使我们将android: fitsSystemWindows属性都设置好了还是没有用的,因为还必 须在程序的主题中将状态栏颜色指定成透明色才行。指定成透明色的方法很简单,在主题中将 android: statusBarColor 属性的值指定成@android:color/transparent 就可以了。但问题 在于,android: statusBarColor这个属性是从API 21,也就是Android 5.0系统开始才有的, 之前的系统无法指定这个属性。那么,系统差异型的功能实现就要从这里开始了。

右击res目录TNew—Directory,创建一个values-v21目录,然后右击values-v21目录一>New

Values resource file,创建一个styles.xml文件。接着对这个文件进行编写,代码如下所示:

<resources>

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

<item name="android:statusBarColor">@android:color/transparent</item> </style>

</resources>

这里我们定义了一个FruitActivityTheme主题,它是专门给FruitActivity使用的oFruitActivity- Theme的parent主题是AppTheme,也就是说,它继承了 AppTheme中的所有特性。然后我们在 FruitActivityTheme中将状态栏的颜色指定成透明色,由于values・v21目录是只有Android 5.0及 以上的系统才会去读取的,因此这么声明是没有问题的。

但是Android 5.0之前的系统却无法识别FruitActivityTheme这个主题,因此我们还需要对 values/styles.xml文件进行修改,如下所示:

<resources>

<!- - Base application theme.-->

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

<!- - Customize your theme here.-->

<item name="colorPrimary">(acolor/colorPrimary</item>

<item name=ucolorPrimaryDark">@color/colorPrimaryDark</item>

<item name="colorAccent">@color/co'lorAccent</item>

</style>

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

</style>

</resources>

可以看到,这里也定义了一个FruitActivityTheme主题,并且parent主题也是AppTheme,但是它的内部是空的。因为Android 5.0之前的系统无法指定状态栏的颜色,因此这里什么都不用做就可以了。

最后,我们还需要让FruitActivity使用这个主题才可以,修改AndroidManifest.xml中的代码, 如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.materialtest">

<application

android:allowBackup="true"

android:icon="@mipmap/ic_launcher"

android:label="@string/appname"

android:supportsRtl="true"

android :theme=,,(astyle/AppTheme">

<activity

android:name=".FruitActivity"

android:theme="@style/FruitActivityTheme">

</activity>

</application>

</manifest>

这里使用android:theme属性单独给FruitActivity指定了 FruitActivityTheme这个主题,这样我们就大功告成了。

现在只要是在Android 5.0及以上的系统运行MaterialTest程序,水果详情展示界面的效果就会如图12.20所示。

相信我,再对比一下图12.17的效果,这两种视觉体验绝对不是在一个档次上的。

12.8小结与点评

        学完了本章的所有知识,你有没有觉得无比兴奋呢?反正我是这么觉得的。本章我们的收获 实在是太多了,从一个什么都没有的空项目,经过一章的学习,最后实现了一个功能如此丰富、 界面如此华丽的应用,还有什么事情比这个更让我们有成就感吗?

        本章中我们充分利用了 Design Support库、support-v4库、appcompat-v7库,以及一些开源项 目来实现一个了高度Material化的应用程序,能将这些库中的相关控件熟练掌握,你的Material Design技术就算是合格了。

        不过说到底,我仍然还是在以一个开发者的思维给你讲解Material Design,侧重于如何去实 现这些效果。而实际上,Material Design的设计思维和设计理念才是更加重要的东西,当然这部 分内容应该是UI设计人员去学习的,如果你也感兴趣的话,可以参考一下Material Design的官方文章:https://material.google.com

        现在你已经足足学习了 12章的内容,对Android应用程序开发的理解应该比较深刻了。目前系统性的知识几乎都已经讲完了,但是还有一些零散的高级技巧在等待着你,那么就让我们赶快进入到下一章的学习当中吧。

第012天:Material Design界面设计语言相关推荐

  1. Android UI开发——Material Design界面设计【详细】

    转自:http://colachan.com/post/3416 找了很多Material Design的资料,终于找到一篇比较靠谱的.能看懂的,我认为非常有用的学习资料,就像他们说的,只要你按Mat ...

  2. 界面设计语言_使用任何语言设计界面的提示

    界面设计语言 Designing for international audiences is challenging. I spent most of my career in Australia ...

  3. Fluent Design System 设计语言元素

    Fluent Design System 设计语言共有元素,分别为光照(Light).深度(Depth).运动(Motion).材质(Material)和缩放(Scale),目前包括人脉.计算器和 G ...

  4. App 界的一股清流 音视频应有尽有 完全按照 Material design 规范设计的 App

    vld 项目地址:Cuieney/vld  简介:App 界的一股清流 音视频应有尽有 完全按照 Material design 规范设计的 App 更多:作者   提 Bug    标签: andr ...

  5. Android UI开发——Material Design界面设计风格

    自从Material design发布以来,可乐橙就在一直收集相关素材与资源,研究别人的作品.这套设计风格非常鲜明,带有浓郁的Google式严谨和理性哲学,深得我心.实际上,光是研究素材和别人作品,就 ...

  6. android 模仿instagram的listview,实现Instagram的Material Design概念设计

    几个月前(这篇文章的日期是2014 年11月10日),google发布了app和web应用的Material Design设计准则之后,设计师Emmanuel Pacamalan在youtube上发布 ...

  7. Material design 色彩

    八月已过去,九月刚来到~暑假已过去~九月上学季~~又迎来了一个桂花飘香的季节,你是否有了新的目标和计划~~所以在九月初始给大家带来一个全新的东西(ps:对于我来说是全新的东西)--Material D ...

  8. 华为云设计语言_《好设计,有方法:我们在搜狐做产品体验设计》 —2.2 设计语言带来的好处...

    2.2 设计语言带来的好处 为什么要提出设计语言的概念呢? 设计语言是设计的基础,是为设计的想象力打好一个地基.设计语言的建立,能在设计层面创建一个全面的视角,帮助整个设计团队遵循相同的方法和模式,确 ...

  9. Material Design之RecyclerView的使用(一)

    Android 5.0開始就推荐使用Material Design这个设计语言,本文開始就逐一解说Material Design中控件的使用.本章主要解说RecyclerView,期中会带有ToolB ...

最新文章

  1. JZOJ 5939. 【NOIP2018模拟10.30】阻击计划
  2. boost::callable_traits添加可变参数的测试程序
  3. C++ 构造函数分类以及调用
  4. 实验心得_大肠杆菌原核表达实验心得(上篇)
  5. MySQL学习(三)
  6. 【福利】3980元的web前端视频教程限量领取!!!
  7. 极度丝滑!CentOS/Unbuntu系统下快速设置虚拟内存,一行命令快速搞定!!!
  8. datagrip替换字_Datagrip 快捷键和常用插件持续更新一集一些使用技巧
  9. python简单查询用户
  10. Javaweb中EL 表达式和JSTL 的使用
  11. win10安装ubuntu虚拟机
  12. 流场(向量场)临界点的检测与分类
  13. 特征工程(五): PCA 降维
  14. 论文笔记:Improving Conversational Recommender Systems via Knowledge Graph based Semantic Fusion(KDD2020)
  15. 云呐|动环监控设备维护与常见故障处理
  16. Java 基础 第3阶段:高级应用——尚硅谷学习笔记(含面试题) 2023年
  17. 第一章 网络操作系统概论
  18. 前端导出pdf以及导出内容截断的解决办法
  19. Java开发工程师的就业方向有哪些?
  20. Python之,评委打分。A~F六位参赛选手,去除最高与最低分之后的的评分为…

热门文章

  1. 他蹲冤狱、斗王石,公司干破产,“死”过好几回,却活出最精彩人生
  2. 段码液晶屏笔段电压范围_可以用段码液晶屏的驱动方式驱动数码管屏吗?
  3. catia 界面语言设置中文和英文切换
  4. Android 可滑动ViewGroup
  5. 开源|商品识别推荐系统
  6. hi3516 编译SSH
  7. kubeadm搭建kubernetes集群(unbuntu18.04)
  8. Tableau图表字典
  9. 数据库建立索引以及索引失效问题
  10. JSP页面上用下横线代替文本框效果的代码