当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找到。
作者