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