一、概述
今天,我們來介紹兩個和側滑選單有關的MD
控制元件:
DrawerLayout
:實現側滑選單的基礎。NavigationView
:作為側滑選單佈局的一種實現方式。
二、DrawerLayout
2.1 基本原理
當我們需要使用到側滑選單時,可以通過DrawerLayout
來實現,DrawerLayout
、側滑選單佈局、普通佈局這三者的關係為:
![Material Design 控制元件知識梳理(5) DrawerLayout && NavigationView](https://i.iter01.com/images/ae273866d964b5d1e0b7577d199b1cb2bf680216ba5110ce8e346d31ff8e80ff.png)
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](https://i.iter01.com/images/d21576de1288fd0ac541aa7af2d9c82ee5ab3e896f9819fb09d2c2234e73bc0f.gif)
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);
}
});
}
複製程式碼
其中opened
和closed
方法都很好理解,就是對應展開和收起,而onDrawerSlide
方法中的slideOffset
,則對應於DrawerLayout
展開的偏移值,全部展開時為1
,全部收起時為0
,onDrawerStateChanged
對應於所處的狀態:STATE_IDLE/STATE_DRAGGING/STATE_SETTLING
。
三、DrawerLayout
和Toolbar
結合使用
3.1 Toolbar
的NavigationIcon
跟隨DrawerLayout
變化
下面,我們看一下把DrawerLayout
和Toolbar
相結合,做出下面的效果,讓Toolbar
的navigationIcon
跟隨著DrawerLayout
變化:
![Material Design 控制元件知識梳理(5) DrawerLayout && NavigationView](https://i.iter01.com/images/4690fb149e1a01957ae520b473c6b13cd45435a36988c6b93d949d06357f8d9d.gif)
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
中,我們需要將Toolbar
和DrawerLayout
關聯起來:
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
,並呼叫Toolbar
的setDisplayHomeAsUpEnabled
,這樣最左邊的Icon
才可以顯示。 - 例項化
ActionBarDrawerToggle
,並傳入Toolbar
和DrawerLayout
。 - 通過
DrawerLayout
的addDrawerListener
方法,讓DrawerLayout
的狀態能夠回撥到ActionBarDrawerToggle
。 - 在
onPostCreate
中,呼叫ActionBarDrawerToggle
的syncState
方法。
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](https://i.iter01.com/images/8effdee6d4ea45ff5acae6b3d0e41d0ca426bf11172193ee505a119a02d8f60f.gif)
3.3 DrawerLayout
和Toolbar
延伸到狀態列上方
如果我們希望側滑選單的區域能夠延伸到狀態列,那麼可以進行以下三步的修改:
- 第一步:修改
Activity
的style
,讓狀態列透明,並修改狀態列的顏色:
<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](https://i.iter01.com/images/7e0a467f57af736ef87d56b505c05b7c065f08e79f7e2c7132b9ea80721e8630.gif)
四、NavigationView
4.1 Navigation
屬性
在使用DrawerLayout
的時候,我們可以隨意定義側滑選單的佈局,NavigationView
其實就是Google
推薦的側滑佈局應該有的樣子,它的效果類似於下面這樣:
![Material Design 控制元件知識梳理(5) DrawerLayout && NavigationView](https://i.iter01.com/images/77f8f6bed5a24b332af7154c62ba9b9d642a4a07fe915757921a4025cb3d0451.gif)
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](https://i.iter01.com/images/e4db9144d5227b6726617acfc48604b3b790b3cee3933e80e0bbb5d844888d27.png)
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
就對應於列表當中的一項,而item
的icon
和title
則分別對應列表項的圖示和文字,如果希望對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](https://i.iter01.com/images/9318e41a6afc051560cf563f6f1f42bb6bbf4075564505160c9a81b968950fde.png)
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](https://i.iter01.com/images/a4017a5e0fadf3b809c422431e9472f57cdabdbb536b90bb8093650813cca118.png)
4.2 Navigation
點選監聽
4.2.1 列表點選監聽
如果希望處理Navigation
中列表的監聽,那麼可以使用現成的介面,根據item
的id
來判斷是點選的是列表中的哪一項。
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
,對其設定監聽,下面是整個headerLayout
在NavigationView
中的層次:
![Material Design 控制元件知識梳理(5) DrawerLayout && NavigationView](https://i.iter01.com/images/369b9f11600357160e0fd279cd529049acbcc4eaaa554fba0afa8751d8f89c60.png)
五、參考文獻
Android 5.0 之 NavigationView 的使用
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/