Material Design 控制元件知識梳理(5) DrawerLayout && NavigationView

澤毛發表於2017-12-21

一、概述

今天,我們來介紹兩個和側滑選單有關的MD控制元件:

  • DrawerLayout:實現側滑選單的基礎。
  • NavigationView:作為側滑選單佈局的一種實現方式。

二、DrawerLayout

2.1 基本原理

當我們需要使用到側滑選單時,可以通過DrawerLayout來實現,DrawerLayout、側滑選單佈局、普通佈局這三者的關係為:

Material Design 控制元件知識梳理(5)   DrawerLayout && NavigationView
layout_gravity決定了將哪個選單作為側滑佈局,DrawerLayout會根據是否宣告瞭layout_gravity屬性,把它內部的直接子View分成兩類:

  • 對於沒有宣告layout_gravity的佈局,那麼它會將它們當作普通佈局,並按照FrameLayout的方式來排列它們。
  • 對於宣告瞭layout_gravity="start"或者layout_gravity="left"的佈局,在普通情況下會將它們隱藏起來,當從螢幕的最左側往右移動手指時,這個佈局會漸漸展現出來,對於DrawerLayout的所有子View來說,只允許有一個子View的該屬性為start/left,這就是我們的側滑佈局。
  • layout_gravity="end/right"和上面類似,只不過它的調出是從螢幕的右側向左側移動。

2.2 簡單事例

下面是一個使用DrawerLayout的最簡單的例子:

<android.support.v4.widget.DrawerLayout
    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"
    tools:context=".DrawerLayoutActivity">
    <!-- 普通佈局 -->
    <FrameLayout
        android:id="@+id/fl_content"
        android:background="@android:color/holo_orange_dark"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:text="我是內容佈局"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </FrameLayout>
    <!-- 側滑佈局 -->
    <include layout="@layout/layout_drawer_normal"/>
</android.support.v4.widget.DrawerLayout>
複製程式碼

layout_drawer_normal就是側滑選單,我們將它的layout_gravity定義為start,按照前面的分析,它應當位於螢幕的左側:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_gravity="start"
    android:layout_width="200dp"
    android:background="@android:color/holo_green_dark"
    android:layout_height="match_parent">
    <TextView
        android:text="我是側滑佈局"
        android:layout_gravity="center"
        android:textColor="@android:color/white"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>
複製程式碼

下面是最終的效果:

Material Design 控制元件知識梳理(5)   DrawerLayout && NavigationView

2.3 監聽DrawerLayout的狀態變化

如果我們希望監聽DrawerLayout狀態的變化,那麼可以通過下面這個方法:

    private Toolbar mToolbar;
    private ActionBarDrawerToggle mActionBarDrawerToggle;
    private DrawerLayout mDrawerLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_drawer_layout_simple);
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {

            @Override
            public void onDrawerSlide(View drawerView, float slideOffset) {
                Log.d("mDrawerLayout", "onDrawerSlide, slideOffset=" + slideOffset);
            }

            @Override
            public void onDrawerOpened(View drawerView) {
                Log.d("mDrawerLayout", "onDrawerOpened");
            }

            @Override
            public void onDrawerClosed(View drawerView) {
                Log.d("mDrawerLayout", "onDrawerClosed");
            }

            @Override
            public void onDrawerStateChanged(int newState) {
                Log.d("mDrawerLayout", "onDrawerStateChanged, state=" + newState);

            }
        });

    }
複製程式碼

其中openedclosed方法都很好理解,就是對應展開和收起,而onDrawerSlide方法中的slideOffset,則對應於DrawerLayout展開的偏移值,全部展開時為1,全部收起時為0onDrawerStateChanged對應於所處的狀態:STATE_IDLE/STATE_DRAGGING/STATE_SETTLING

三、DrawerLayoutToolbar結合使用

3.1 ToolbarNavigationIcon跟隨DrawerLayout變化

下面,我們看一下把DrawerLayoutToolbar相結合,做出下面的效果,讓ToolbarnavigationIcon跟隨著DrawerLayout變化:

Material Design 控制元件知識梳理(5)   DrawerLayout && NavigationView
當拖動側邊欄的時候,Toolbar的按鈕會跟隨著進行狀態的變化,我們也可以通過點選Toolbar的按鈕來展開和收起側邊欄,首先看我們的佈局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- Toolbar -->
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:background="@android:color/holo_green_dark"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"/>
    <android.support.v4.widget.DrawerLayout
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <!-- 普通佈局 -->
        <FrameLayout
            android:id="@+id/fl_content"
            android:background="@android:color/holo_orange_dark"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:text="我是內容佈局"
                android:layout_gravity="center"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </FrameLayout>
        <!-- 側滑佈局 -->
        <include layout="@layout/layout_drawer_normal"/>
    </android.support.v4.widget.DrawerLayout>
</LinearLayout>
複製程式碼

Activity中,我們需要將ToolbarDrawerLayout關聯起來:

public class DrawerLayoutActivity extends AppCompatActivity {

    private Toolbar mToolbar;
    private ActionBarDrawerToggle mActionBarDrawerToggle;
    private DrawerLayout mDrawerLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_drawer_layout_under_toolbar);
        initView();
    }

    private void initView() {
        mToolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(mToolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true); //1.決定顯示.
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mActionBarDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.drawer_open, R.string.drawer_close); //2.傳入Toolbar可以點選.
        mDrawerLayout.addDrawerListener(mActionBarDrawerToggle); //3.監聽變化.
    }

    @Override
    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        //4.同步狀態
        mActionBarDrawerToggle.syncState();
    }
}
複製程式碼

這裡需要做的有四步工作:

  • Toolbar作為ActionBar,並呼叫ToolbarsetDisplayHomeAsUpEnabled,這樣最左邊的Icon才可以顯示。
  • 例項化ActionBarDrawerToggle,並傳入ToolbarDrawerLayout
  • 通過DrawerLayoutaddDrawerListener方法,讓DrawerLayout的狀態能夠回撥到ActionBarDrawerToggle
  • onPostCreate中,呼叫ActionBarDrawerTogglesyncState方法。

3.2 DrawerLayout覆蓋Toolbar

在上面的實現方式中,側滑選單位於Toolbar的下方,如果我們希望它覆蓋Toolbar,那麼可以像下面這樣佈局:

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- 普通佈局 -->
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:background="@android:color/holo_green_dark"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"/>
        <FrameLayout
            android:id="@+id/fl_content"
            android:background="@android:color/holo_orange_dark"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:text="我是內容佈局"
                android:layout_gravity="center"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </FrameLayout>
    </LinearLayout>
    <!-- 側滑佈局 -->
    <include layout="@layout/layout_drawer_normal"/>
</android.support.v4.widget.DrawerLayout>
複製程式碼

最終的效果為:

Material Design 控制元件知識梳理(5)   DrawerLayout && NavigationView

3.3 DrawerLayoutToolbar延伸到狀態列上方

如果我們希望側滑選單的區域能夠延伸到狀態列,那麼可以進行以下三步的修改:

  • 第一步:修改Activitystyle,讓狀態列透明,並修改狀態列的顏色:
<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorAccent">@color/colorAccent</item>
        <!-- 修改狀態列顏色 -->
        <item name="colorPrimaryDark">@android:color/holo_green_dark</item>
        <!-- 狀態列透明 -->
        <item name="android:windowTranslucentStatus">true</item>
    </style>
</resources>
複製程式碼
  • 第二步:給Activity根佈局設定android:fitsSystemWindows="true"屬性
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <!-- 普通佈局 -->
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:background="@android:color/holo_green_dark"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"/>
        <FrameLayout
            android:id="@+id/fl_content"
            android:background="@android:color/holo_orange_dark"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:text="我是內容佈局"
                android:layout_gravity="center"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </FrameLayout>
    </LinearLayout>
    <!-- 側滑佈局 -->
    <include layout="@layout/layout_drawer_normal"/>
</android.support.v4.widget.DrawerLayout>
複製程式碼
  • 第三步:給側滑佈局設定android:fitsSystemWindows="true"屬性
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_gravity="start"
    android:layout_width="200dp"
    android:background="@android:color/holo_green_dark"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <TextView
        android:text="我是側滑佈局"
        android:layout_gravity="center"
        android:textColor="@android:color/white"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>
複製程式碼

通過以上三步,就可以達到沉浸式狀態列的效果:

Material Design 控制元件知識梳理(5)   DrawerLayout && NavigationView

四、NavigationView

4.1 Navigation屬性

在使用DrawerLayout的時候,我們可以隨意定義側滑選單的佈局,NavigationView其實就是Google推薦的側滑佈局應該有的樣子,它的效果類似於下面這樣:

Material Design 控制元件知識梳理(5)   DrawerLayout && NavigationView
我們先來看一個使用Navigation的簡單例子:

<android.support.design.widget.NavigationView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="300dp"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    app:headerLayout="@layout/layout_drawer_navigation_header"
    app:menu="@menu/menu_navigation"
    app:itemIconTint="@android:color/white"
    app:itemBackground="@android:color/holo_green_dark"
    app:itemTextColor="@android:color/black">
</android.support.design.widget.NavigationView>
複製程式碼

上面app各屬性對應到下圖中就是這樣:

Material Design 控制元件知識梳理(5)   DrawerLayout && NavigationView
可以看到,Navigation分為兩個部分:頭部和列表。

  • 頭部是app:headerLayout所指定的佈局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <ImageView
        android:src="@drawable/ic_bg"
        android:scaleType="centerCrop"
        android:layout_width="match_parent"
        android:layout_height="200dp"/>
</LinearLayout>
複製程式碼
  • 列表通過app:menu所指定:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/favorite"
        android:icon="@mipmap/ic_launcher"
        android:title="收藏"/>
    <item
        android:id="@+id/wallet"
        android:icon="@mipmap/ic_launcher"
        android:title="錢包"/>
    <item
        android:id="@+id/photo"
        android:icon="@mipmap/ic_launcher"
        android:title="相簿"/>
    <item
        android:id="@+id/file"
        android:icon="@mipmap/ic_launcher"
        android:title="檔案"/>
</menu>
複製程式碼

menu當中的每個item就對應於列表當中的一項,而itemicontitle則分別對應列表項的圖示和文字,如果希望對item進行分組,那麼可以採用group的方式來組織menu

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:id="@+id/group1">
        <item
            android:id="@+id/favorite"
            android:icon="@mipmap/ic_launcher"
            android:title="group1 - item1"/>
        <item
            android:id="@+id/wallet"
            android:icon="@mipmap/ic_launcher"
            android:title="group1 - item2"/>
    </group>
    <group android:id="@+id/group2">
        <item
            android:id="@+id/photo"
            android:icon="@mipmap/ic_launcher"
            android:title="group2 - item1"/>
        <item
            android:id="@+id/file"
            android:icon="@mipmap/ic_launcher"
            android:title="group2 - item2"/>
    </group>
</menu>
複製程式碼

不同組之間就會被分割線隔開:

Material Design 控制元件知識梳理(5)   DrawerLayout && NavigationView
如果想要給某個分組新增標題,那麼可以採用subMenu的方式:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:id="@+id/group1">
        <item
            android:id="@+id/item1"
            android:icon="@mipmap/ic_launcher"
            android:title="group1 - item1"/>
        <item
            android:id="@+id/item2"
            android:icon="@mipmap/ic_launcher"
            android:title="group1 - item2"/>
    </group>
    <group android:id="@+id/group2">
        <item
            android:id="@+id/item3"
            android:icon="@mipmap/ic_launcher"
            android:title="group2 - item1"/>
        <item
            android:id="@+id/item4"
            android:icon="@mipmap/ic_launcher"
            android:title="group2 - item2"/>
    </group>
    <item
        android:id="@+id/item5"
        android:title="group3">
        <menu>
            <item
                android:id="@+id/item6"
                android:icon="@mipmap/ic_launcher"
                android:title="group3 - item1"/>
            <item
                android:id="@+id/item7"
                android:icon="@mipmap/ic_launcher"
                android:title="group3 - item2"/>
        </menu>
    </item>
</menu>
複製程式碼

Material Design 控制元件知識梳理(5)   DrawerLayout && NavigationView

4.2 Navigation點選監聽

4.2.1 列表點選監聽

如果希望處理Navigation中列表的監聽,那麼可以使用現成的介面,根據itemid來判斷是點選的是列表中的哪一項。

mNavigationView = (NavigationView) findViewById(R.id.navigation);
mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        Log.d("onSelected", "id=" + item.getItemId());
        return true;
    } 
});
複製程式碼

4.2.2 頭部點選監聽

NavigationView並沒有提供頭部點選的監聽回撥,因此,我們只能夠通過findViewById的方法找到對應的View,對其設定監聽,下面是整個headerLayoutNavigationView中的層次:

Material Design 控制元件知識梳理(5)   DrawerLayout && NavigationView

五、參考文獻

Android 5.0 之 NavigationView 的使用


更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章