吐槽
作為一個 Android developer,沒有什么比拿着 UI 設計的一堆 iOS 風格的設計 來做需求更惡心的了,基本所有空間都要照着 iOS 來畫一遍,Material Design 辣么酷炫 為什么 UI在設計的階段不設計成 Material Design風格呢?
今天試了幾個比較Support包中比較典型的Material Design控件,后期會在學習下Material Design的設計思想和理念,希望能拉着 UI 做一次Material Design 分享,改變我們 APP 的 iOS 風格啊。
最終效果如下

Android Design Support 庫依賴
在 build.gradle
中加入support 包
1
|
compile 'com.android.support:appcompat-v7:23.1.1'
|
Design Support Library 中包含了 Support v4 和 AppCompat v7

我們希望FloatingActionButton
懸浮在頁面的右下方,所以我們父節點應使用Flowlayout
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="io.github.xuyushi.materialdesigndemo.MainActivity">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" />
<android.support.design.widget.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:src="@android:drawable/ic_dialog_email" />
</FrameLayout>
|
和普通 button 一樣可以設置其點擊事件
1 2 3 4 5 6 7 8 9
|
private void initFb() { mFb = (FloatingActionButton) findViewById(R.id.fb); mFb.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "fb predsed ", Toast.LENGTH_SHORT).show(); } }); }
|
Android:elevation
屬性為 view 在空閑狀態下的陰影深度, 需要在 api 21以上才能使用,使用 support 包可以使用app:elevation
來表示空閑狀態高度,app:pressedTanslationZ
為按下狀態的高度
按鈕的顏色一般為主題的強調色,也可以使用 ”app:backgroundTint“修改
Snackbar
和 Toast 很像,snackbar 可以展示一段簡單的信息,不同點是它的展示更像是整體 UI 的一部分,不是想 toast 一樣是浮在 UI 上的,並且可以有簡單的交互
在點擊 floatingActionButton時顯示Snackbar

但是可以看到,Snackbar 遮擋住了我們的 view,這時候需要一個CoordinatorLayout
來協調 view 布局
CoordinatorLayout
將父布局中的framelaout
換成CoordinatorLayout
,其他不變,再來看看效果

==todo CoordinatorLayout學習==
Toolbar 比傳統的 ActionBar 更靈活,功能也更多,我們可以看到現在市面上很多的 APP 已經用 Toolbar 替代了 actionbar,在 Desgin Support 的組件中,很多設計都可以和 Toolbar 協同工作,而不是和 actionbar,所以還是建議使用新的 toolbar 替換以前的 actionbar
替換步驟
1、在 minifest 中,將 activity 的 apptheme 的 style 中的 actionbar屬性去掉
1 2 3 4 5 6 7 8 9 10 11 12
|
<style name="AppTheme.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
|
- 在 fb 之前放入 Toolbar 組件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
<android.support.design.widget.CoordinatorLayout .........
<android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" />
<android.support.design.widget.FloatingActionButton android:id="@+id/fb" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end|bottom" android:src="@android:drawable/ic_dialog_email" app:elevation="12dp" app:pressedTranslationZ="30dp" />
</android.support.design.widget.CoordinatorLayout>
|
- 通知系統使用 toolbar
1 2 3 4
|
private void initToolbar() { mToolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(mToolbar); }
|
CoordinatorLayout
中的 view 必須是能一同協作的 view,就像 Snackbar 一樣,但是 toolbar 並不是這樣能協同作戰的 view,所以我們需要用一個可以協同作戰的 view 來包裹上Toolbar
,這就是 AppBarLayout
現在我們的布局文件結構是這樣的
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
<android.support.design.widget.CoordinatorLayout ...> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.Toolbar .../> </android.support.design.widget.AppBarLayout> <android.support.design.widget.FloatingActionButton ...> </android.support.design.widget.FloatingActionButton> </android.support.design.widget.CoordinatorLayout>
|
注意
根據官方的谷歌文檔,AppBarLayout目前必須是第一個嵌套在CoordinatorLayout里面的子view
在 toolbar 中加入屬性,app:layout_collapseMode=”pin”,使得 Toolbar 中的按鈕能固定在頂部
在布局中加入內容
在布局中嘗試加入一些按鈕
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
.... </android.support.design.widget.AppBarLayout>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:orientation="vertical">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="test" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="test" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="test" />
</LinearLayout> ...
|

我們定義三個按鍵,卻被 toolbar 遮住了一個,原因是LinearLayout
並沒有被設計成在CoordinatorLayout
協同工作的模式,為了使他們能在CoordinatorLayout
協同工作,我們需要在LinearLayout
加上一條屬性,來告訴它的滾動屬性()
1 2 3 4 5
|
<LinearLayout ... app:layout_behavior="@string/appbar_scrolling_view_behavior" ... >
|
搞定
TabLayout
根據官網的知道,TabLayout
通常應該是放在頂部,(iOS 的 tab 好像基本在底部),
他應該在陰影部分上面,所以應該放在AppBarlayout
中
1 2 3 4 5 6 7
|
<android.support.design.widget.AppBarLayout ...> <android.support.v7.widget.Toolbar ... /> <android.support.design.widget.TabLayout android:id="@+id/tabLayout" android:layout_width="match_parent" android:layout_height="wrap_content"/> </android.support.design.widget.AppBarLayout>
|
java 中設置這些 tab 屬性
1 2 3 4 5 6
|
private void initTableLayout() { mTabLayout = (TabLayout) findViewById(R.id.tabLayout); mTabLayout.addTab(mTabLayout.newTab().setText("Tab 1")); mTabLayout.addTab(mTabLayout.newTab().setText("Tab 2")); mTabLayout.addTab(mTabLayout.newTab().setText("Tab 3")); }
|

背景會設置為主題色,導航線是強調色。但是字還是黑色的,因為我們沒有為 tablayout 定義主題,
1 2 3
|
<android.support.design.widget.TabLayout ... app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
|
通常 tablayout 會和ViewPager
一起使用 ,這時候使用
public void setupWithViewPager (ViewPager viewPager)
一張圖看的比較清晰

內容滾動時,AppBarLayout隱藏
當滑檔內容時,為了騰出跟多的空間展示內容可以將AppBarLayout
隱藏
1.用 scrollView 包裹 LinearLayout,記得加上 app:layout_behavior
屬性
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
<ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" app:layout_behavior="@string/appbar_scrolling_view_behavior" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > ... </LinearLayout> </ScrollView>
|
- Toolbar 加上屬性
1 2 3
|
<android.support.v7.widget.Toolbar ... app:layout_scrollFlags="scroll|enterAlways" />
|
- scrollView 也不能喝 CoordinatorLayout 協同工作,同上面一樣,要用別的 view 包裹或者直接使用 NestedSrollView替換scrollView

如果希望 tablayout 也消失,只需要和 tablayout 加上相同的屬性就行了
1 2 3
|
<android.support.design.widget.TabLayout ... app:layout_scrollFlags="scroll|enterAlways" />
|
滑動內容 和 AppBarLayout是如何進行聯系的?
我們需要定義AppBarLayout與滾動視圖之間的聯系。在RecyclerView或者任意支持嵌套滾動的view比如NestedScrollView上添加app:layout_behavior。support library包含了一個特殊的字符串資源@string/appbar_scrolling_view_behavior,它和AppBarLayout.ScrollingViewBehavior相匹配,用來通知AppBarLayout 這個特殊的view何時發生了滾動事件,這個behavior需要設置在觸發事件(滾動)的view之上
當CoordinatorLayout發現scrollView中定義了這個屬性,它會搜索自己所包含的其他view,看看是否有view與這個behavior相關聯。AppBarLayout.ScrollingViewBehavior描述了RecyclerView與AppBarLayout之間的依賴關系。RecyclerView的任意滾動事件都將觸發AppBarLayout或者AppBarLayout里面view的改變。
AppBarLayout里面定義的view只要設置了app:layout_scrollFlags屬性,就可以在RecyclerView滾動事件發生的時候被觸發:
app:layout_scrollFlags屬性里面必須至少啟用scroll這個flag,這樣這個view才會滾動出屏幕,否則它將一直固定在頂部。可以使用的其他flag有:
- enterAlways: 一旦向上滾動這個view就可見。
- enterAlwaysCollapsed: 顧名思義,這個flag定義的是何時進入(已經消失之后何時再次顯示)。假設你定義了一個最小高度(minHeight)同時enterAlways也定義了,那么view將在到達這個最小高度的時候開始顯示,並且從這個時候開始慢慢展開,當滾動到頂部的時候展開完。
- exitUntilCollapsed: 同樣顧名思義,這個flag時定義何時退出,當你定義了一個minHeight,這個view將在滾動到達這個最小高度的時候消失。
記住,要把帶有scroll flag的view放在前面,這樣收回的view才能讓正常退出,而固定的view繼續留在頂部。
- 用 CollapsingToolbarLayout 包裹 Toolbar,但仍然在 AppBarLayout 中
- 刪除 Toolbar 中的 layout_scrollFlags
- 為 CollapsingToolbarLayout 聲明 layout_scrollFlags,並且將 layout_scrollFlags 設置成 scroll|exitUntilCollapsed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
<android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="256dp"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsingToolbarLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:minHeight="?attr/actionBarSize" app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:layout_collapseMode="pin"/> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout>
|
注意 CollapsingToolbarLayout 的高度是android:layout_height="match_parent"
CollapsingToolbarLayout在展開和收縮時,標題的文字會自動過度的,可以通過 app:expandedTitleMargin 等來改變文字位置

為 appBar 添加背景圖片
由於 CollapsingToolbarLayout 是繼承 Framelayout 的,所以我們可以直接添加一個 ImageView 作為背景圖片
1 2 3 4 5 6 7 8
|
<ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/header" /> <android.support.v7.widget.Toolbar ...
|
此時雖然背景已經出來了,但是藍色的導航條依舊存在,需要在 toolbar 去掉這條屬性
1
|
android:background="?attr/colorPrimary"
|

給 Imageview 加上視差模式會更帥
1 2
|
app:layout_collapseMode="parallax" app:layout_collapseParallaxMultiplier="0.5"
|
也可以在最后恢復成主題色
1 2 3
|
<android.support.design.widget.CollapsingToolbarLayout ... app:contentScrim="?attr/colorPrimary">
|

Navigation Drawer
在AppBarLayout
布局下,增DrawerLayout
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
<android.support.design.widget.AppBarLayout> ...
<android.support.v4.widget.DrawerLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:openDrawer="start">
<include layout="@layout/content_main" />
<android.support.design.widget.NavigationView android:id="@+id/nav_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" app:headerLayout="@layout/nav_header_main2" app:menu="@menu/activity_main2_drawer" />
</android.support.v4.widget.DrawerLayout>
|
DrawerLayout
中分兩部分組成,一部分是content 就是我們需要的主布局內容,另一部分是我們的抽屜的布局,NavigationView中有頂部頭,和標簽
1 2
|
app:headerLayout="@layout/nav_header_main2" app:menu="@menu/activity_main2_drawer"
|
創建菜單。
菜單元素是放在group標簽之下,同時聲明每次只能有一個item被選中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single"> <item android:id="@+id/nav_camera" android:icon="@drawable/ic_menu_camera" android:title="Import" /> <item android:id="@+id/nav_gallery" android:icon="@drawable/ic_menu_gallery" android:title="Gallery" /> <item android:id="@+id/nav_slideshow" android:icon="@drawable/ic_menu_slideshow" android:title="Slideshow" /> <item android:id="@+id/nav_manage" android:icon="@drawable/ic_menu_manage" android:title="Tools" /> </group>
<item android:title="Communicate"> <menu> <item android:id="@+id/nav_share" android:icon="@drawable/ic_menu_share" android:title="Share" /> <item android:id="@+id/nav_send" android:icon="@drawable/ic_menu_send" android:title="Send" /> </menu> </item>
</menu>
|
為了防止頁面被遮蓋,同樣要使得 DrawerLayout
協調。加入app:layout_behavior="@string/appbar_scrolling_view_behavior"
屬性
java初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
private void initDraw() { DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.setDrawerListener(toggle); toggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem item) { |

SwipeRefreshLayout
在NestedScrollView
外在包裹一層SwipeRefreshLayout
,
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/refresh" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" > ..... </android.support.v4.widget.NestedScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
|
初始化監聽器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
private void initRefresh() { mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh); mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { refreshContent(); } }); }
private void refreshContent() { new Handler().postDelayed(new Runnable() { @Override public void run() { mSwipeRefreshLayout.setRefreshing(false); } }, 2000); }
|
