当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的文章:

这就是为什么本文只会蜻蜓点水般的讲解从自定义实现到Design Support Library实现的过渡。顺便,我们也会看到我们可以节省多少代码。

NavigationView

navigation_view.gif

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

fab.gif

浮动操作按钮可能是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。

结果

appbarlayout.gif

简单,是吧?现在我们来看看使用其余的scrollFlags可以做出啥效果。

Snackbar

snackbar.gif

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

tablayout.gif

谷歌总算给了我们一个比较摩登的tabbar实现方法 - 具有横向滚动,图标或者文字,简单的tab indicator自定义,tab的gravity,波纹效果等等。这就是TabLayout,不多不少。

tab_layout.xml 

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

TabLayout.java 

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会被这个颜色所遮挡。

现在看一眼最终的效果:

Collapsing Toolbar

这就是今天的全部内容。我们只是用新的view或者效果来更新了InstaMaterial,比如Snackbar,FloatingActionButton, TabLayout, CoordinatorLayout, AppBarLayout and CollapsingToolbarLayout。它们全部都是Android Design Support Library提供的。

源码

全部的项目代码可以在Github repository找到。

作者

Miroslaw Stanek

来自:InstaMaterial概念设计