当InstaMaterial遇到Design Support Library
原文:InstaMaterial meets Design Support Library
几个月前,我开始写InstaMaterial 系列文章。目的很简单-就是为了证明实现Material Design规范中的任何炫酷UI效果其实都很容易。现在变的更容易了 - 谷歌给我们Android Design Support Library ,里面提供了几乎所有重要的Material Design UI元素。在本文,我希望把InstaMaterial的源码从自定义view的实现方式转到用 Design Support Library 的方式。
前言
design support library 是否意味着这里描述的所有代码都已经过时了呢?并不完全。的确,使用官方的实现方式总是要好些(真的吗?),尤其是在标准的使用案例中。使用别人提供的实现方式意味着有人帮你考虑代码的问题。我们不需要考虑浮动操作按钮(Floating Action Button)在Kitkat, Lollipop 以及 M___之间的兼容性问题。多亏了它我们节省了不少的代码量,从而有时间去实现更炫的东西(或者让代码更健壮)。
但是!另一方面讲,有一个很重要的问题,这些库的作者也是和我们一样的程序员,他们也会犯错,他们无法预知所有的使用场景。还有一点更重要 - 有时候知道底层原理更好些-知其然还要知其所以然。只为更好的理解“系统”是如何工作的。
认识 desing support library
目前有许多探讨design support library的文章:
-
Antonio Leiva的博客上的几篇文章
-
…可能还有更多
这就是为什么本文只会蜻蜓点水般的讲解从自定义实现到Design Support Library实现的过渡。顺便,我们也会看到我们可以节省多少代码。
NavigationView
Material design准则对于默认的Navigation drawer定义非常清楚。因为有了NavigationView,整个菜单可以直接在res/menu/{filename}.xml中实现。我们只需要在Activity中使用如下代码而不是自定义view结构:
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/flContentRoot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView
android:id="@+id/vNavigation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#ffffff"
app:headerLayout="@layout/view_global_menu_header"
app:itemIconTint="#8b8b8b"
app:itemTextColor="#666666"
app:menu="@menu/drawer_menu" />
</android.support.v4.widget.DrawerLayout>
NavigationView有两个非常重要的属性: app:headerLayout 和 app:menu。第一个定义了被用作navigation menu头部的自定义view布局。第二个定义了谁来提供菜单元素。下面展示了我们app中菜单的实现:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/menu_group_1">
<item
android:id="@+id/menu_feed"
android:icon="@drawable/ic_global_menu_feed"
android:title="My Feed" />
<item
android:id="@+id/menu_direct"
android:icon="@drawable/ic_global_menu_direct"
android:title="Instagram Direct" />
<item
android:id="@+id/menu_news"
android:icon="@drawable/ic_global_menu_news"
android:title="News" />
<item
android:id="@+id/menu_popular"
android:icon="@drawable/ic_global_menu_popular"
android:title="Popular" />
<item
android:id="@+id/menu_photos_nearby"
android:icon="@drawable/ic_global_menu_nearby"
android:title="Photos Nearby" />
<item
android:id="@+id/menu_photo_you_liked"
android:icon="@drawable/ic_global_menu_likes"
android:title="Photos You've Liked" />
</group>
<group android:id="@+id/menu_group_2">
<item
android:id="@+id/menu_settings"
android:title="Settings" />
<item
android:id="@+id/menu_about"
android:title="About" />
</group>
</menu>
在我们的app中我们还定义了一个BaseDrawerActivity,每个继承自它的activity都会自动添加navigation drawer 。这里是BaseDrawerActivity的源码。
最后,我们看一看this commit。自定义navigation drawer 视图,menu adapter以及布局,这些事情都变得没有必要了。
Floating Action Button
浮动操作按钮可能是Material Design规范中最引人注目的UI元素了。但是直到现在它的实现方式都不是很明确。圆形,阴影(也是圆形的),波纹(ripple)效果,elevation(层次感)。现在所有这些library都已经提供了。我们只需要把FloatingActionButton直接放在.xml布局文件中就可以了:
<android.support.design.widget.FloatingActionButton
android:id="@+id/btnCreate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginBottom="@dimen/btn_fab_margins"
android:layout_marginRight="@dimen/btn_fab_margins"
android:src="@drawable/ic_instagram_white"
app:borderWidth="0dp"
app:elevation="6dp"
app:pressedTranslationZ="12dp" />
就是这么简单。我们不需要为Lollipop和Lollipop之前的设备准备两种drawable,不需要寻求圆形阴影的解决办法等等。我们再也不希望用不常用的方式去自定义它,所有的都为我们做好了。
值得一提的是FloatingActionButton默认实现了两种尺寸-mini和普通(默认),分别用app:fabSize="mini" 和 app:fabSize="normal"定义。
CoordinatorLayout
这是很多程序员的期望已久的控件-尤其是那些致力于交互式布局的程序员。CoordinatorLayout是一个新的ViewGroup布局,用于帮助其子view之间的协作与交互。实际使用中最常用的用例可能是ScrollView和其他view之间的交互了。这也是我们想以此为开始的东西-我们希望在向下滚动的时候隐藏Toolbar,向上滚动的时候则显示。
AppBarLayout
AppBarLayout则是另一个帮助我们实现此效果的另一个ViewGroup。它为toolbar赋予了几个默认的behavior,可以用于和任意的滚动视图交互。下面是一个演示了如何使用AppBarLayout与CoordinatorLayout联系起来的布局示例:
<android.support.design.widget.CoordinatorLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvFeed"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.AppBarLayout
android:id="@+id/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"
app:elevation="@dimen/default_elevation"
app:layout_scrollFlags="scroll|enterAlways"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<ImageView
android:id="@+id/ivLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:scaleType="center"
android:src="@drawable/img_toolbar_logo" />
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
这里有几个重要的事情:
-
Toolbar中的app:layout_scrollFlags="scroll|enterAlways" 意味着AppBarLayout的这个子view可以响应滚动事件(view随着滚动事件滚动)同时任意向下的滚动都将让view变的可见(快速返回模式)。
-
app:layout_behavior="@string/appbar_scrolling_view_behavior"在RecyclerView中有这个属性意味着这个滚动视图将会发送滚动事件到AppBarLayout的子view。
结果
简单,是吧?现在我们来看看使用其余的scrollFlags可以做出啥效果。
Snackbar
Snackbar被认为是一种更成熟的Toast实现。它也被用作可以提供额外操作(比如针对当前操作的“Undo”操作)的短消息提示。此外它还可以和嵌套在CoordinatorLayout中的其他view交互。
其构造和Toast一样简单:
public void showLikedSnackbar() {
Snackbar.make(clContent, "Liked!", Snackbar.LENGTH_SHORT).show();
}
make() 方法的第一个参数是一个view,从该view找到一个parent。意味着Snackbar将向上走一遍这个view树,寻找一个合适的父亲。如果是CoordinatorLayout,Snackbar将可以和FloatingActionButton交互。同时它还变的可以通过划动删除。
TabLayout
谷歌总算给了我们一个比较摩登的tabbar实现方法 - 具有横向滚动,图标或者文字,简单的tab indicator自定义,tab的gravity,波纹效果等等。这就是TabLayout,不多不少。
<android.support.design.widget.TabLayout
android:id="@+id/tlUserProfileTabs"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="?attr/colorAccent"
app:tabGravity="fill"
app:tabIndicatorColor="#5be5ad"
app:tabIndicatorHeight="4dp"
app:tabMode="fixed" />
private void setupTabs() {
tlUserProfileTabs.addTab(tlUserProfileTabs.newTab().setIcon(R.drawable.ic_grid_on_white));
tlUserProfileTabs.addTab(tlUserProfileTabs.newTab().setIcon(R.drawable.ic_list_white));
tlUserProfileTabs.addTab(tlUserProfileTabs.newTab().setIcon(R.drawable.ic_place_white));
tlUserProfileTabs.addTab(tlUserProfileTabs.newTab().setIcon(R.drawable.ic_label_white));
}
CollapsingToolbarLayout
在本文的最后我们将看看CollapsingToolbarLayout。自从Toolbar成为比ActionBar更普遍的解决方法的时候,出现了许多和此view相关的UI效果-视差(parallax),动态的标题大小以及位置,可以扩展或者折叠 的内容等等。这就是CollapsingToolbarLayout可以为我们做的事情。
而且一样的是全 .xml布局配置-完全没有java代码。
下面是替换UserProfileAdapter的实现(不重要的代码隐藏了):
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<LinearLayout
android:id="@+id/vUserProfileRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
app:layout_collapseMode="parallax">
</LinearLayout>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll|enterAlways"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
<android.support.design.widget.TabLayout
android:id="@+id/tlUserProfileTabs"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="?attr/colorAccent"
app:tabGravity="fill"
app:tabMode="fixed" />
</android.support.design.widget.AppBarLayout>
activity_user_profile.xml的全部代码在这里。
重点?
-
app:layout_scrollFlags="scroll|exitUntilCollapsed" 意味着在滚动到顶部之前不固定的view会一直折叠起来。固定的view(Pinned views)(具有 app:layout_collapseMode="pin"属性的Toolbar ) 将持不可触摸状态。
-
app:layout_collapseMode="parallax" 意味着我们的view将会以视差方式折叠。CollapsingToolbarLayout中的app:contentScrim="?attr/colorPrimary"意味着折叠的view会被这个颜色所遮挡。
现在看一眼最终的效果:
这就是今天的全部内容。我们只是用新的view或者效果来更新了InstaMaterial,比如Snackbar,FloatingActionButton, TabLayout, CoordinatorLayout, AppBarLayout and CollapsingToolbarLayout。它们全部都是Android Design Support Library提供的。
源码
全部的项目代码可以在Github repository找到。