Material Design 實戰 之第二彈——滑動選單詳解&實戰(DrawerLayout & NavigationView)
本模組共有六篇文章,參考郭神的《第一行程式碼》,對Material Design的學習做一個詳細的筆記,大家可以一起交流一下:
- ofollow,noindex">Material Design 實戰 之第一彈——Toolbar(即本文)
- Material Design 實戰 之第二彈——滑動選單詳解&實戰
- Material Design 實戰 之第三彈—— 懸浮按鈕和可互動提示(FloatingActionButton & Snackbar & CoordinatorLayout)
- Material Design 實戰 之第四彈 —— 卡片佈局以及靈動的標題欄(CardView & AppBarLayout)
- Material Design 實戰 之第五彈 —— 下拉重新整理(SwipeRefreshLayout)
- Material Design 實戰 之 第六彈 —— 可摺疊式標題欄(CollapsingToolbarLayout) & 系統差異型的功能實現(充分利用系統狀態列空間)
文章提要與總結
1. DrawerLayout 控制元件用處:實現滑動選單 1.1 首先它是一個佈局,在佈局中允許放入兩個直接子控制元件, 第一個子控制元件是主螢幕中顯示的內容; 第二個子控制元件是滑動選單中顯示的內容; 關於第二個子控制元件有一點需要注意,layout_gravity這個屬性是必須指定的:leftrightstart 1.2 新增導航按鈕: 1.2.1 首先呼叫findViewById()方法得到了DrawerLayout的例項; 1.2.2 getSupportActionBar()方法得到了ActionBar的例項; 1.2.3 呼叫ActionBar的setDisplayHomeAsUpEnabled()讓導航按鈕顯示出來; 1.2.4 呼叫了setHomeAsUpIndicator()方法來設定一個導航按鈕圖示; 1.2.5 在onOptionsItemSelected()中對HomeAsUp按鈕的點選事件進行處理——呼叫DrawerLayout的openDrawer()方法將滑動選單展示出來; 注意openDrawer()方法要求傳入一個Gravity引數, 為了保證這裡的行為和XML中(DrawerLayout標籤下的第二個直接子控制元件的android:layout_gravity值)定義的一致, 我們傳入了GravityCompat.START; 1.2.6 實際上Toolbar最左側的這個按鈕就叫作HomeAsUp按鈕,它預設的圖示是一個返回的箭頭,含義是返回上一個活動; 這裡將其換了圖示,並將邏輯響應修改了; HomeAsUp按鈕的id永遠都是android.R.id.home!!! 2. NavigationView 控制元件用處:輕鬆佈局華麗炫酷的滑動選單頁面; 2.1 添加了兩行依賴關係
compile 'com.android.support:design:24.2.1' compile 'de.hdodenhof:circleimageview:2.1.0'
2.2 在開始使用NavigationView之前,我們還需要提前準備好兩個東西:menu和headerLayout。 2.2.1 menu是用來在NavigationView中顯示具體的選單項的; 為Menu resource file; 在<menu>中嵌套了一個<group>標籤 <group>標籤下的<item>: android:id屬性指定選單項的id, android:icon屬性指定選單項的圖示, android:title屬性指定選單項顯示的文字。 2.2.2 headerLayout則是用來在NavigationView中顯示頭部佈局的。 為Layout resourcefile; 2.3 使用NavigationView 新增android.support.design.widget.NavigationView標籤,
使用app:menu="@menu/nav_menu" app:headerLayout="@layout/nav_header" 將menu和headerLayout設定完畢
正文

文末成品圖
DrawerLayout
關於滑動選單和DrawerLayout,郭神如是說:


<?xml version="1.0" encoding="utf-8"?> <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"> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> </FrameLayout> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="start" android:text="This is menu" android:textSize="30sp" android:background="#FFF"/> </android.support.v4.widget.DrawerLayout>
可見這裡最外層的控制元件使用了DrawerLayout,這個控制元件是由support-v4庫提供的。
DrawerLayout中放置了兩個直接子控制元件:
第一個子控制元件是FrameLayout,用於作為主螢幕中顯示的內容,當然裡面還有我們剛剛定義的Toolbar。
第二個子控制元件這裡使用了一個TextView,用於作為滑動選單中顯示的內容,其實使用什麼都可以,DrawerLayout並沒有限制只能使用固定的控制元件。
但是關於第二個子控制元件有一點需要注意,layout_gravity這個屬性是必須指定的,
因為我們需要告訴DrawerLayout滑動選單是在螢幕的左邊還是右邊,
指定left表示滑動選單在左邊;
指定right表示滑動選單在右邊;
這裡指定了start,表示會根據系統語言進行判斷,如果系統語言是從左往右的,比如英語、漢語,滑動選單就在左邊,如果系統語言是從右往左的,比如阿拉伯語,滑動選單就在右邊。
現在重新執行一下程式,然後在螢幕的左側邊緣向右拖動,就可以讓滑動選單顯示出來了,如圖:





這裡我們並沒有改動多少程式碼,
- 首先呼叫findViewById()方法得到了DrawerLayout的例項,
- 然後呼叫 getSupportActionBar() 方法得到了ActionBar的例項,雖然這個 ActionBar 的具體實現是由 Toolbar 來完成的。
- 接著呼叫ActionBar的 setDisplayHomeAsUpEnabled()方法 讓導航按鈕顯示出來,
- 又呼叫了 setHomeAsUpIndicator() 方法來設定一個導航按鈕圖示。
實際上,Toolbar最左側的這個按鈕就叫作HomeAsUp按鈕,它預設的圖示是一個返回的箭頭,含義是返回上一個活動。很明顯,這裡我們將它預設的樣式(該按鈕圖示)和作用(改/設定了按鈕點選事件)都進行了修改。
接下來在onOptionsItemSelected()方法中對HomeAsUp按鈕的點選事件進行處理,
HomeAsUp按鈕的id永遠都是android.R.id.home;
切記是 android.R.id.home ,如果寫成 R.id.home 是實現不了功能的!
然後呼叫DrawerLayout的openDrawer()方法將滑動選單展示出來;
注意openDrawer()方法要求傳入一個Gravity引數,為了保證這裡的行為和XML中定義的一致,我們傳入了 GravityCompat.START ;
當前MainActivity全文:
public class MainActivity extends AppCompatActivity { private DrawerLayout mDrawerLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBar actionBar = getSupportActionBar(); if(actionBar != null){ actionBar.setDisplayHomeAsUpEnabled(true);//讓導航按鈕顯示出來 actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);//設定一個導航按鈕圖示 } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.toolbar,menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case android.R.id.home: mDrawerLayout.openDrawer(GravityCompat.START); case R.id.backup: Toast.makeText(this,"You clicked Backup" , Toast.LENGTH_SHORT).show(); break; case R.id.delete: Toast.makeText(this,"You clicked Delete" , Toast.LENGTH_SHORT).show(); break; case R.id.settings: Toast.makeText(this,"You clicked Settings" , Toast.LENGTH_SHORT).show(); break; default: } return true; } }
執行程式,效果如下:

可見在Toolbar的最左邊出現了一個導航按鈕,使用者看到這個按鈕就知道這肯定是可以點選的。
現在點選一下這個按鈕,滑動選單介面就會再次展示出來了。
NavigationView



首先這個控制元件是DesignSupport庫中提供的,需要將這個庫引入到專案中。
開啟app/build.gradle檔案,在dependencies閉包中新增依賴:

compile 'com.android.support:design:24.2.1' compile 'de.hdodenhof:circleimageview:2.1.0'
這裡添加了兩行依賴關係,
第一行就是DesignSupport庫,
第二行是一個開源專案CircleImageView,它可以用來輕鬆實現圖片圓形化的功能,我們待會就會用到它。
CircleImageView的專案主頁地址是: https://github.com/hdodenhof/CircleImageView 。
!!!
在開始使用NavigationView之前,我們還需要提前準備好兩個東西: menu和headerLayout 。
menu是用來在NavigationView中顯示具體的選單項的;
headerLayout則是用來在NavigationView中顯示頭部佈局的。
1/4.準備menu
我們先來準備menu,這裡我事先找了幾張圖片來作為按鈕的圖示,並將它們放在了drawable-xxhdpi目錄下。然後右擊menu資料夾→New→Menu resource file,建立一個nav_menu.xml檔案,並編寫如下程式碼:
<?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/nav_call" android:icon="@drawable/nav_call" android:title="Call"/> <item android:id="@+id/nav_friends" android:icon="@drawable/nav_friends" android:title="Friends"/> <item android:id="@+id/nav_location" android:icon="@drawable/nav_location" android:title="Location"/> <item android:id="@+id/nav_mail" android:icon="@drawable/nav_mail" android:title="Mail"/> <item android:id="@+id/nav_task" android:icon="@drawable/nav_task" android:title="Tasks"/> </group> </menu>

我們首先在<menu>中嵌套了一個<group>標籤,
然後將group的checkableBehavior屬性指定為singlegroup表示一個組,
checkableBehavior指定為single表示組中的所有選單項只能單選;
那麼下面我們來看一下這些選單項吧。這裡一共定義了5個item,
分別使用
android:id屬性指定選單項的id,
android:icon屬性指定選單項的圖示,
android:title屬性指定選單項顯示的文字。
就是這麼簡單,現在我們已經把menu準備好了。
2/4.準備headerLayout
接下來應該準備headerLayout了,這是一個可以隨意定製的佈局,不過這裡不將它做得太複雜。我們就在headerLayout中放置頭像、使用者名稱、郵箱地址這3項內容吧;
說到頭像,那我們還需要再準備一張圖片,這裡找了一張寵物圖片,並把它放在了drawable-xxhdpi目錄下。
另外這張圖片最好是一張正方形圖片,因為待會我們會把它圓形化。
然後右擊layout資料夾→New→Layout resourcefile,建立一個nav_header.xml檔案。
修改其中的程式碼,如下所示:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="180dp" android:padding="10dp" android:background="?attr/colorPrimary"> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/icon_image" android:layout_width="70dp" android:layout_height="70dp" android:src="@drawable/nav_icon" android:layout_centerInParent="true"/> <TextView android:id="@+id/mail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:text="8267*****@qq.com" android:textColor="#FFF" android:textSize="14sp"/> <TextView android:id="@+id/username" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/mail" android:text="wanchuangxiaoyun" android:textColor="#FFF" android:textSize="14sp"/> </RelativeLayout>
可以看到,佈局檔案的最外層是一個RelativeLayout,我們將它的
寬度設為match_parent,
高度設為180dp,
這是一個NavigationView比較適合的高度,然後
指定它的背景色為colorPrimary;
在RelativeLayout中我們放置了3個控制元件,
CircleImageView是一個用於將圖片圓形化的控制元件,它的用法非常簡單,基本和ImageView是完全一樣的,這裡給它指定了一張圖片作為頭像,然後設定為居中顯示。
另外兩個TextView分別用於顯示使用者名稱和郵箱地址,它們都用到了一些RelativeLayout的定位屬性;
3/4.使用NavigationView
現在menu和headerLayout都準備好了,我們終於可以使用NavigationView了。
修改activitymam.xml中的程式碼,如下所示:
<?xml version="1.0" encoding="utf-8"?> <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"> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> </FrameLayout> <android.support.design.widget.NavigationView android:id="@+id/nav_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="start" app:menu="@menu/nav_menu" app:headerLayout="@layout/nav_header"> </android.support.design.widget.NavigationView> </android.support.v4.widget.DrawerLayout>
可見這裡將之前的TextView換成了NavigationView,這樣滑動選單中顯示的內容也就變成NavigationView了。
這裡又通過app:menu和app:headerLayout將我們剛才準備好的menu和headerLayout設定了進去,
這樣NavigationView就定義完成了。
接下來還要去處理選單項的點選事件。修改MainActivity中的程式碼:

程式碼還是比較簡單的,
這裡首先獲取到了NavigauonView的例項,
然後呼叫它的setCheckedItem()方法將Call選單項設定為預設選中。
接著呼叫了setNavigationItemSelectedListener()方法來設定一個選單項選中事件的監聽器,當用戶點選了任意選單項時,就會回撥到onNavigationItemSelected()方法中。
我們可以在這個方法中寫相應的邏輯處理,不過這裡並沒有附加任何邏輯,只是呼叫了DrawerLayout的closeDrawers()方法將滑動選單關閉,這也是合情合理的做法。
下面是當前MainActivity.java的全文:
public class MainActivity extends AppCompatActivity { private DrawerLayout mDrawerLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); NavigationView navView = (NavigationView) findViewById(R.id.nav_view); ActionBar actionBar = getSupportActionBar(); if(actionBar != null){ actionBar.setDisplayHomeAsUpEnabled(true);//讓導航按鈕顯示出來 actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);//設定一個導航按鈕圖示 } navView.setCheckedItem(R.id.nav_call);//將Call選單項設定為預設選中 navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener(){ @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { mDrawerLayout.closeDrawers();//關閉滑動選單 return true; } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.toolbar,menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.home: mDrawerLayout.openDrawer(GravityCompat.START); case R.id.backup: Toast.makeText(this,"You clicked Backup" , Toast.LENGTH_SHORT).show(); break; case R.id.delete: Toast.makeText(this,"You clicked Delete" , Toast.LENGTH_SHORT).show(); break; case R.id.settings: Toast.makeText(this,"You clicked Settings" , Toast.LENGTH_SHORT).show(); break; default: } return true; } }
現在可以重新執行一下程式了,點選一下Toolbar左側的導航按鈕,效果如圖所示:

怎麼樣?這樣的滑動選單頁面,你無論如何也不能說它醜了吧?MaterialDesign的魅力就在
這裡,它真的是一種非常美觀的設計理念,只要你按照它的各種規範和建議來設計介面,最終做
出來的程式就是特別好看的。
——郭霖大神