Android Material Design 系列之 DrawerLayout + NavigationView 使用詳解

前言

DrawerLayout 是 Support Library 包中實現了側滑菜單效果的控件,可以說 DrawerLayout 是因爲第三方控件如 MenuDrawer 等的出現之後,google 借鑑而出現的產物。DrawerLayout 分爲側邊菜單和主內容區兩部分,側邊菜單可以根據手勢展開與隱藏(DrawerLayout 自身特性),主內容區的內容可以隨着菜單的點擊而變化。

一、DrawerLayout 基礎使用

DrawerLayout 其實是一個佈局控件,繼承 ViewGroup,與 LinearLayout 等控件是一種東西,屬於同級控件。但是 DrawerLayout 帶有滑動的功能。只要按照 DrawerLayout 的規定佈局方式寫完佈局,就能有側滑的效果。

DrawerLayout 最簡單的使用方式,添加 2 個子佈局,分別代表 APP 主頁面和側滑菜單頁面。

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!--主頁面-->
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@mipmap/meizi_2" />
    <!--側滑菜單頁面-->
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@mipmap/pangzi" />
</androidx.drawerlayout.widget.DrawerLayout>

完成上面的佈局文件,就可以實現如下效果:

二、DrawerLayout + ToolBar 使用

日常開發中,每個界面都會有一個 ToolBar,並且側滑出來的內容都會在 ToolBar 的底部。最常見的就是 ToolBar 左側會有一個小圖標(號稱三道槓),在側滑菜單展示時,會加載一個動畫變成返回按鈕,完成這個效果不需要在 ToolBar 上自己添加 Icon,只需要藉助 ActionBarDrawerToggle 類就可實現。

ActionBarDrawerToggle 效果就是一個“三“ 然後點擊變”←“

ActionBarDrawerToggle 的作用:

  1. 改變 android.R.id.home 返回圖標
  2. DrawerLayout 拉出、隱藏,帶有 android.R.id.home 動畫效果
  3. 監聽 DrawerLayout 拉出、隱藏

因爲要實現側滑菜單在 ToolBar 底部,修改 XML 文件,將 ToolBar 放在 DrawerLayout 佈局外層。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:layout_scrollFlags="scroll|enterAlways"
        app:title="DrawerLayout"
        tools:ignore="MissingConstraints" />

    <androidx.drawerlayout.widget.DrawerLayout
        android:id="@+id/drawerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:src="@mipmap/meizi_2" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:src="@mipmap/pangzi" />

    </androidx.drawerlayout.widget.DrawerLayout>
</LinearLayout>
// 設置左上角圖標["三" —— "←"]效果
ActionBarDrawerToggle actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close);
actionBarDrawerToggle.syncState();
drawerLayout.addDrawerListener(actionBarDrawerToggle);

三、NavigationView 介紹

NavigationView 是 Google 在側滑的 Material Design 的一種規範,所以提出了一個新的空間,用來規範側滑菜單的基本樣式。

對於抽屜式菜單界面很多 APP 都有應用,此前寫抽屜式界面都需要自定義。現在谷歌提供的導航視圖 NavigationView + DrawerLayout 結合使用,能提供很好的側滑交互體驗。

四、NavigationView 常用方法

方法 介紹
addHeaderView(View view) 將視圖添加爲導航菜單的標題
getHeaderCount() 獲取此 NavigationView 中標頭的數量
getHeaderView(int index) 獲取指定位置的標題視圖
getMenu() 返回 Menu 與此導航視圖關聯的實例
inflateMenu(int resId) 在此導航視圖中添加菜單資源
removeHeaderView(View view) 刪除先前添加的標題視圖
setItemBackgroundResource(int resId) 將菜單項的背景設置爲給定資源
setItemIconSize(int iconSize) 設置用於菜單項圖標的大小(以像素爲單位)
setItemTextAppearance(int resId) 將菜單項的文本外觀設置爲給定資源
setItemTextColor(ColorStateList textColor) 設置要在菜單項上使用的文本顏色
setItemIconTintList(ColorStateList tint) 設置菜單項上使用 Icon 的顏色
     setNavigationItemSelectedListener(NavigationView listener)                            設置一個偵聽器,當選擇菜單項時將用來通知該偵聽器                     

五、NavigationView 基礎使用

DrawerLayout + NavigationView + ToolBar 結合使用是項目中最常見的效果,通常有 2 種效果:

  • 側滑菜單在 ToolBar 底部

  • 側滑菜單沉浸式覆蓋 ToolBar 展示

第一種效果實際上就是在 XML 佈局中,將 ToolBar 佈局放到 DrawerLayout 外部,第二種效果是將 ToolBar 放到 DrawerLayout 內部主頁面佈局裏面,然後實現沉浸式效果。本文源碼中有沉浸式實現的工具類,感興趣的朋友可以下載源碼參考

1、XML 佈局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:title="DrawerLayout"
            app:titleTextColor="#FFF"
            tools:ignore="MissingConstraints" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:src="@mipmap/meizi_2" />

    </LinearLayout>

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/navigationView"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/nav_header_main"
        app:insetForeground="@android:color/transparent"
        app:menu="@menu/activity_main_drawer" />

</androidx.drawerlayout.widget.DrawerLayout>

只需在 DrawerLayout 中添加 NavigationView 控件即可,其中介紹兩個屬性(也就是上圖中紅黃藍三個位置效果):

  • app:insetForeground="@android:color/transparent" NavigationView 沉浸式展示

  • app:headerLayout="@layout/nav_header_main" 在 NavigationView 上添加一個 Header 佈局

  • app:menu="@menu/activity_main_drawer" NavigationView 添加標籤 Item 的菜單

2、HeaderLayout 佈局文件

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="200dp"
    android:background="?attr/colorPrimary"
    android:gravity="bottom"
    android:theme="@style/ThemeOverlay.AppCompat.Dark">

    <com.caobo.slideviewdemo.drawerlayout.MovingImageView
        android:id="@+id/movingImageView"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:scaleType="centerCrop"
        android:src="@mipmap/menu_header_background"
        app:miv_load_on_create="false"
        app:miv_max_relative_size="3.0"
        app:miv_min_relative_offset="0.2"
        app:miv_repetitions="-1"
        app:miv_speed="100"
        app:miv_start_delay="100" />

    <de.hdodenhof.circleimageview.CircleImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="30dp"
        android:paddingTop="16dp"
        android:src="@mipmap/header_icon"
        app:civ_border_color="@color/colorWhite"
        app:civ_border_width="2dp" />

    <TextView
        android:id="@+id/tv_nick"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="16dp"
        android:paddingLeft="5dp"
        android:text="Learn and live."
        android:textAppearance="@style/TextAppearance.AppCompat.Body1"
        android:textSize="18sp" />
</FrameLayout>

3、Menu 菜單文件

這裏的 icon 圖標全部使用 vector 矢量圖展示,如果還不會使用 Vector 矢量圖的朋友,可以參考:

Android Material Design Icon Genenerator 插件爲個人開發者提供 Icon 圖標大全

<?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/group_item_github"
            android:icon="@drawable/ic_vector_github_grey"
            android:title="項目主頁" />
        <item
            android:id="@+id/group_item_more"
            android:icon="@drawable/ic_vector_more"
            android:title="更多內容" />
        <item
            android:id="@+id/group_item_qr_code"
            android:icon="@drawable/ic_vector_qr_code"
            android:title="二維碼" />
        <item
            android:id="@+id/group_item_share_project"
            android:icon="@drawable/ic_vector_share"
            android:title="分享項目" />
    </group>
    <item android:title="選項">
        <menu>
            <item
                android:id="@+id/item_model"
                android:icon="@drawable/ic_vetor_setting"
                android:title="夜間模式" />
            <item
                android:id="@+id/item_about"
                android:icon="@drawable/ic_vector_about"
                android:title="關於" />
        </menu>
    </item>
</menu>

4、Activity 代碼實現

NavigationView 的使用基本上都是 XML 文件中完成的,Activity 中不需要做太多處理,只需要 Activity 中添加 Menu 的監聽。本項目中使用了一個帶動畫的 ImageView,所以側滑菜單後,底部圖片會有動畫效果,感興趣的可以在底部下載源碼學習。

public class DrawerLayoutActivity extends BaseActivity {

    @BindView(R.id.toolbar)
    Toolbar toolbar;
    @BindView(R.id.drawerLayout)
    DrawerLayout drawerLayout;
    @BindView(R.id.navigationView)
    NavigationView navigationView;
    MovingImageView movingImageView;
    private ActionBarDrawerToggle actionBarDrawerToggle;

    @Override
    protected void initView() {
        movingImageView = navigationView.getHeaderView(0).findViewById(R.id.movingImageView);
        // 設置左上角圖標["三" —— "←"]效果
        actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close);
        actionBarDrawerToggle.syncState();
        drawerLayout.addDrawerListener(actionBarDrawerToggle);

        // 設置不允許 NavigationMenuView 滾動
        NavigationMenuView navigationMenuView = (NavigationMenuView) navigationView.getChildAt(0);
        if (navigationMenuView != null) {
            navigationMenuView.setVerticalScrollBarEnabled(false);
        }
        // NavigationView 監聽
        navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.group_item_github:
                        Toast.makeText(DrawerLayoutActivity.this,"項目主頁",Toast.LENGTH_SHORT).show();
                        break;
                    case R.id.group_item_more:
                        Toast.makeText(DrawerLayoutActivity.this,"更多內容",Toast.LENGTH_SHORT).show();
                        break;
                    case R.id.group_item_qr_code:
                        Toast.makeText(DrawerLayoutActivity.this,"二維碼",Toast.LENGTH_SHORT).show();
                        break;
                    case R.id.group_item_share_project:
                        Toast.makeText(DrawerLayoutActivity.this,"分享項目",Toast.LENGTH_SHORT).show();
                        break;
                    case R.id.item_model:
                        Toast.makeText(DrawerLayoutActivity.this,"夜間模式",Toast.LENGTH_SHORT).show();
                        break;
                    case R.id.item_about:
                        Toast.makeText(DrawerLayoutActivity.this,"關於",Toast.LENGTH_SHORT).show();
                        break;
                }
                item.setCheckable(false);
                drawerLayout.closeDrawer(GravityCompat.START);
                return true;
            }
        });
    }

    @Override
    protected int getLayoutResID() {
        return R.layout.activity_drawerlayout;
    }
}

六、NavigationView 全屏效果

1、設置全屏顯示

NavigationView 設置 android:layout_width="match_parent" 發現仍然無法實現全屏展示效果,如果想要 NavigationView 實現全屏展示,代碼中按照如下設置:

ViewGroup.LayoutParams mLayoutParams = navigationView.getLayoutParams();
int width = getResources().getDisplayMetrics().widthPixels;
mLayoutParams.width = width;
navigationView.setLayoutParams(mLayoutParams);

2、仿 QQ 側滑菜單效果

根據 NavigationView 全屏效果,模仿 QQ 個人中心側滑菜單效果,其實我們發現 QQ 這麼牛逼的軟件,也是使用 Google 原生控件實現,可想而知 Material Design 系列控件之強大。QQ 側滑菜單實現由以下兩點組成:

  1. QQ 側滑菜單鋪滿全屏
  2. 主頁面跟隨菜單一起滑動

第一點我們已經實現,只需要上面 4 行代碼就可以設置 NavigationView 全屏效果,接下來只需要將主頁面跟隨菜單一起滑動就可以實現效果,我們回想前面講到的 DrawerLayout.addDrawerListener() 方法,可以監聽事件,其中有 4 個回調方法:

  • onDrawerSlide(View drawerView, float slideOffset) 當抽屜的位置改變時調用

  • onDrawerOpened(View drawerView) 打開側滑界面觸發

  • onDrawerClosed(View drawerView) 關閉側滑界面觸發

  • onDrawerStateChanged(int newState) 狀態改變時觸發

根據上面 4 個回調方法,要實現 QQ 效果,需要在 onDrawerSlide(View drawerView, float slideOffset) 方法中進行處理,其中 slideOffset 返回的是抽屜菜單從隱藏到打開的偏移,取值 0~1,drawerView 就是側邊菜單佈局,具體實現代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@mipmap/qq_1" />

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/navigationView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/nav_header_main"
        app:insetForeground="@android:color/transparent"
        app:menu="@menu/activity_main_drawer" />

</androidx.drawerlayout.widget.DrawerLayout>
drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
      @Override
      public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
          // 主頁內容
          View contentView = drawerLayout.getChildAt(0);
          // 側邊欄
          View menuView = drawerView;
          // slideOffset 值默認是0~1
          contentView.setTranslationX(menuView.getMeasuredWidth() * slideOffset);
      }

      @Override
      public void onDrawerOpened(@NonNull View drawerView) {}

      @Override
      public void onDrawerClosed(@NonNull View drawerView) {}

      @Override
      public void onDrawerStateChanged(int newState) {}
  });

提示:仿 QQ 側滑菜單效果,這裏沒有使用 ToolBar,直接截取了 2 張圖片展示。

七、手動控制菜單

默認手勢側滑就可以 Open 菜單,點擊空白處 Close。但是避免需要點擊事件觸發菜單狀態,DrawerLayout 設計非常人性化,提供了以下方法來實現:

  1. openDrawer(View drawerView)

    打開指定的摺疊項視圖,將其動畫到視圖中。

  2. closeDrawer(View drawerView)

    關閉指定的摺疊項視圖,將其動畫到視圖中。

  3. closeDrawers()

    關閉所有當前打開的抽屜視圖,通過動畫他們的視野。

使用舉例:

// 打開左邊菜單
mDrawerLayout.openDrawer(GravityCompat.START);
// 打開右邊菜單
mDrawerLayout.openDrawer(GravityCompat.END);
// 關閉左邊菜單6
mDrawerLayout.closeDrawer(GravityCompat.START);
// 關閉所有菜單
mDrawerLayout.closeDrawers();

源碼下載 源碼包含 Material Design 系列控件集合,定時更新,敬請期待!

八、總結

Android Material Design Library 推出了很長時間,越來越多的 APP 使用了符合 Library 包的控件,DrawerLayout 絕對是熱門之一,Material Design 定義了一個抽屜導航應該有何種外觀和感受,統一了側滑菜單和樣式。在 Android 原生手機上對 DrawerLayout+NavigationView 更是使用到了極致。

我的微信:Jaynm888

歡迎點評,誠邀 Android 程序員加入微信交流羣,公衆號回覆加羣或者加我微信邀請入羣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章