2016年8月3日 星期三

Material Design on android

Ref : http://blog.30sparks.com/material-design-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:23.1.1'      compile 'com.android.support:recyclerview-v7:23.1.1'      compile 'com.android.support:design:23.1.1'      compile 'com.github.clans:fab:1.6.0'      compile 'jp.wasabeef:recyclerview-animators:1.2.0@aar'      compile 'com.viewpagerindicator:library:2.4.1@aar'
}
Navigation Drawer,通常是從左邊邊緣拉出來的一個導覽選單,是一個很常用到的 UI 元件。







很多 open source library 都在做這東西,不過既然 Google 提供,我們暫時又沒有等別的要求,便用它的吧。用 Design Library 來做到此效果,需用到 DrawerLayout 和 NavgationViewDrawerLayout 用來做從左到右拉出來的抽屜效果,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"
                /abc_action_bar_default_height_material"
                android:background="?attr/colorPrimary"
                android:minHeight="?attr/actionBarSize"
                /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"
            /drawer_header"
            /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>




菜單是簡單的 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));
這樣就算旋轉螢幕多少次也沒有問題。
code implement :
先看  MainActivity.java
@Overrideprotected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    initDrawerLayout();
    initNavView(savedInstanceState);

}
這裡他呼叫了兩個function   
initDrawerLayout() 和  initNavView(savedInstanceState);

initDrawerLayout() 是在初始化  navigation bar.....


initDrawerLayout() 的code 如下面,  其實大概就跟上面的講解一樣

這兩個變數類別變數
DrawerLayout drawerLayout;
TextView contentView;


private void initDrawerLayout(){
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    //  為 textview()
    contentView = (TextView) findViewById(R.id.content_view);

    // 宣告在  activity_main.xml 的一開始
    drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);


    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();
}











從上面的這張圖來看....

下面得那些 navigator bar 的item 就是在這邊指定

/menu/drawer.xml

<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"/>
    <item        android:id="@+id/navigation_item_appbar"        android:icon="@drawable/ic_launcher"        android:title="AppBarLayout Activity" />
    <item        android:id="@+id/navigation_item_tab"        android:icon="@drawable/ic_launcher"        android:title="Tab Layout Activity" />
    <item        android:id="@+id/navigation_item_another_fab"        android:icon="@drawable/ic_launcher"        android:title="Another FAB" />
    <item        android:id="@+id/navigation_item_vpi"        android:icon="@drawable/ic_launcher"        android:title="ViewPagerIndicator Activity" />
</group>


然後是在 activity_main.xml 的這邊指定


<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"   ->  指定header    app:menu="@menu/drawer"/>                   ->  指定下面的item


至於上面的  header

則是在   drawer_header.xml 裡面指定...



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

沒有留言:

張貼留言