Material Design Patterns 教學 (1) - Navigation Drawer
想有漂亮的 Material Design,其實 Google 已提供 Android Design Support Library 可供使用。它支援 Android 2.1 或以上,提供不少好用的 UI element,可方便做到 Material Design Pattern 的效果。我們在此逐一介紹 (可以一段時間不用再煩惱寫什麼,Yeah! 用 code 代替一部份內容,寫少很多,Yeah Yeah!)。
安裝
Android Design Support Library 可通過 Gradle 來安裝:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:design:22.2.1'
}
Navigation Drawer
Navigation Drawer,通常是從左邊邊緣拉出來的一個導覽選單,是一個很常用到的 UI 元件。
很多 open source library 都在做這東西,不過既然 Google 提供,我們暫時又沒有等別的要求,便用它的吧。用 Design Library 來做到此效果,需用到 DrawerLayout
和 NavgationView
。DrawerLayout
用來做從左到右拉出來的抽屜效果,NavigationView
用來在拉出來的畫面上顯示用戶資料和導覽選單。
準備
在 Android Studio 新增一個 project,為它加入 MainActivity
。開啟 AndroidManifest.xml
確認使用 android:theme
為 @style/AppTheme
。再開啟 res/values/style.xml
,將 AppTheme
的 parent 改為 Theme.AppCompat.Light.NoActionBar
,並加入以下 items。這是因為我們之後會用 ToolBar
來做 ActionBar
。
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#212121</item>
<item name="colorPrimaryDark">#187817</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowActionModeOverlay">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
設定 layout
然後設定 layout。DrawerLayout
的用法很簡單,它裏面只能包著兩個 view
,第一個是主要內容的 view
,第二個是拉出來的導覽菜單 view
。最基本的設定如下:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- the content layout -->
<LinearLayout
android:id="main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
</LinearLayout>
<!-- the drawer layout -->
<LinearLayout
android:id="drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
我們會以 LinearLayout
加上 Toolbar
和 TextView
來作為主要內容。至於導覽菜單,你可以用 LinearLayout
來自行設計一個漂亮的外觀,也可以只用一個 ListView
來顯示。我們這裏自然用 NavigationView
。
NavigationView
分兩上下兩部份,上面的是 headerLayout
,可以自設任何內容。下面的為 menu 部份,會載入 res/menu
下的 menu。經它點選了的 Menu item 會自動被 highlight,不用自己寫 code 記著,比起用 ListView
等無異更方便。
最後的 activity_main.xml
如下:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- your content layout -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/abc_action_bar_default_height_material"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<TextView
android:id="@+id/content_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World" />
</LinearLayout>
<android.support.design.widget.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/drawer_header"
app:menu="@menu/drawer" />
</android.support.v4.widget.DrawerLayout>
NavigationView
中的 headerLayout
指向 res/layout/drawer_header.xml
。 app:menu
是將會載入的 menu item。
headerLayout
你可自行為它加入背景圖片或個人頭像,弄得跟 Gmail 的一樣也可。為求簡單,我們只用 TextView
加上背景色。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="190dp"
android:background="#0097a7"
>
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginStart="24dp"
android:layout_alignParentBottom="true"
android:text="Eddard Stark"
android:textSize="14sp"
android:textColor="#fff"
android:textStyle="bold"
android:paddingBottom="8dp"
/>
</RelativeLayout>
也算不錯啦。
設定 menu
菜單是簡單的 menuItem
,放在 /res/menu/drawer.xml
:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="@+id/navigation_item_1"
android:checked="true"
android:icon="@drawable/ic_launcher"
android:title="Navigation Items 1"/>
<item
android:id="@+id/navigation_item_2"
android:icon="@drawable/ic_launcher"
android:title="Navigation Items 2"/>
<item
android:id="@+id/navigation_item_3"
android:icon="@drawable/ic_launcher"
android:title="Navigation Items 3"/>
</group>
</menu>
group
的 checkableBehavior
設為 single
,代表只會 highlight 一個 menuItem
。
主菜
因為不是使用 onCreateOptionsMenu()
來載入導覽菜單,所以不能用 onOptionsItemSelected()
來設定反應,要加上 OnNavigationItemSelectedListener
來操作。在 onCreate()
中加入以下 coding:
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
contentView = (TextView) findViewById(R.id.content_view);
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
NavigationView view = (NavigationView) findViewById(R.id.navigation_view);
view.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override public boolean onNavigationItemSelected(MenuItem menuItem) {
Toast.makeText(MainActivity.this, menuItem.getTitle() + " pressed", Toast.LENGTH_LONG).show();
contentView.setText(menuItem.getTitle());
menuItem.setChecked(true);
drawerLayout.closeDrawers();
return true;
}
});
我們將 toolbar
設成 ActionBar
。並為 NavigationView
加上 OnNavigationItemSelectedListener
,這樣按 menuItem
的話,便會顯示一個 toast
,和將 contentView
的文字設為 menuItem
的 title。
執行來試一試,可看到 drawerLayout
可拉出來,按 menu 也會有反應。
但平時拉出 drawerLayout
時看到三條線變箭嘴 icon 的效果要怎麼做呢?
加上「三」圖示
這個便要用 actionBarDrawerToggle
來做了。先到 onCreate()
中將 ToolBar
設為 ActionBar
。
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
然後加上 actionBarDrawerToggle
:
ActionBarDrawerToggle actionBarDrawerToggle = new ActionBarDrawerToggle( this, drawerLayout, toolbar, R.string.openDrawer , R.string.closeDrawer){
@Override
public void onDrawerClosed(View drawerView) {
super .onDrawerClosed(drawerView);
}
@Override
public void onDrawerOpened(View drawerView) {
super .onDrawerOpened(drawerView);
}
};
drawerLayout.setDrawerListener(actionBarDrawerToggle);
actionBarDrawerToggle.syncState();
這樣便完成了。
Bonus - Configuration Change
有沒有發覺旋轉螢幕後 menu 和 contentView
會被重設?這是因為旋轉螢幕等於 Configuration Change,Configuration Change 後 Activity
會被消滅然後重生,等於執行了 onDestory()
後再 onCreate()
。除非有特別處理,否則所有 variable 都會被 reset。
要如何記著我剛才按了的第三個 menu ?這便要用 onSaveInstanceState()
。
首先在 MainActivity
加一個 member variable 用來儲存點擊了的 menuItem
id,
private int navItemId;
然後將之前 OnNavigationItemSelectedListener
中更新畫面的工作搬到一個新 method 中。因為除了 OnNavigationItemSelectedListener
會執行它外,之後 onCreate()
也會執行,所以將此 code 放在一個 method 裏會方便一點。
private void navigateTo(MenuItem menuItem){
contentView.setText(menuItem.getTitle());
navItemId = menuItem.getItemId();
menuItem.setChecked(true);
}
注意這裏會用 navItemId
記著當前的 menuItem
。
之後再用 onSaveInstanceState()
來記著當前的 navItemId
:
private static final String NAV_ITEM_ID = "nav_index";
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(NAV_ITEM_ID, navItemId);
}
最後在 onCreate()
讀取便可
if(null != savedInstanceState){
navItemId = savedInstanceState.getInt(NAV_ITEM_ID, R.id.navigation_item_1);
}
else{
navItemId = R.id.navigation_item_1;
}
navigateTo(view.getMenu().findItem(navItemId));
這樣就算旋轉螢幕多少次也沒有問題。
結語
以下的程式碼已放上 Github:
https://github.com/goofyz/android-material-design-tutorial/tree/part1_navigation_view
Navigation Drawer 通常是用作 top level navigation 的,別只為漂亮而加進你的 app 中,要想清楚 User Experience 才好。
下次我們講講 Floating Action Button。