Android抽屜式導航欄使用及相關類認識
抽屜式導航欄官方推出好久了,之前也使用過,但對這些類都是懵懵懂懂的。今天認真看了下文件才把這些類搞清楚。個人對MD的設計風格還是很喜歡的。
DrawerLayout
DrawerLayout是一個視窗內容的頂層容器(像LinearLayout、FrameLayout這些一樣),它的用處在於允許從視窗的一個或兩個垂直邊緣拉出互動式“抽屜”檢視(這個抽屜指的就是滑出的View,說抽屜還挺形象的啊)。也就是說要實現滑出View的話,你得使用這個ViewGroup作為頂層佈局。
抽屜定位和佈局使用android:layout_gravity屬性來控制,該屬性決定抽屜檢視從哪個方向滑出:左側或右側(基於支援佈局方向的平臺版本,例如有些地區閱讀方向是從右往左,要考慮這些情況可以統一使用start而不是left)。每個垂直邊緣只能有一個抽屜檢視。如果佈局在視窗的每個垂直邊緣配置多個抽屜檢視,則會在執行時丟擲異常。
- 要使用DrawerLayout,需要將主內容檢視作為第一個子節點(因為在xml檔案中佈局將在z軸上進行覆蓋,後面的佈局覆蓋前面的,這裡必須保證滑出檢視在主檢視之上)。
- 主內容檢視寬度和高度為match_parent而且不要使用layout_gravity。主內容檢視設定為匹配父檢視的寬度和高度, 因為在抽屜式導航欄處於隱藏狀態時, 它和平時UI介面沒什麼兩樣。
- 在主內容檢視之後新增抽屜檢視(滑出的View),並設定layout_gravity,這決定檢視從那邊滑出,如果不設定將直接顯示而不是滑出。抽屜通常使用match_parent為高度,寬度為固定值(寬度不應超過 320dp,從而使用者始終可以看到部分主內容)。
根據“Android設計指南”,任何位於左側(基於閱讀順序)的抽屜應當是對應用程式中內容的導航,而位於右側的抽屜是對當前內容所執行的操作。
使用示例
<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" >
<!-- 主內容檢視,通常由抽屜中的item點選來決定使用相應的fragment進行填充 -->
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- 抽屜檢視,可以為任意檢視 -->
<ListView android:id="@+id/left_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start" <!-- 指定滑出方向,關鍵點 -->
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:background="#111"/>
</android.support.v4.widget.DrawerLayout>
新增監聽器
抽屜中item的點選事件
這決定於你在抽屜中所使用的View,用常規方式為它們新增點選事件即可。下面是使用ListView作為抽屜View的例子:
//這是官方的例子。這裡對ListView.OnItemClickListener進行了一層封裝,不封裝直接使用也是可以的 private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectItem(position); } } /* 這裡以切換fragment為例 */ private void selectItem(int position) { // Create a new fragment and specify the planet to show based on position Fragment fragment = new PlanetFragment(); Bundle args = new Bundle(); args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position); fragment.setArguments(args); // Insert the fragment by replacing any existing fragment FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.content_frame, fragment) .commit(); //將item置為選中狀態,更新標題,隱藏抽屜 mDrawerList.setItemChecked(position, true); setTitle(mPlanetTitles[position]); mDrawerLayout.closeDrawer(mDrawerList); } @Override public void setTitle(CharSequence title) { mTitle = title; getActionBar().setTitle(mTitle); }
監聽抽屜狀態
通過下面的步驟新增監聽器:
- 首先繫結DrawerLayout取到它的引用。
- 呼叫
addDrawerListener
方法並傳入一個DrawerLayout.DrawerListener
的例項。(注意:原有的setDrawerListener()` 方法已被移除) - 複寫
DrawerLayout.DrawerListener
一系列方法即可。
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); drawer.addDrawerListener(new DrawerLayout.DrawerListener() { //滑動時觸發,會多次呼叫 @Override public void onDrawerSlide(View drawerView, float slideOffset) { Log.d(TAG, "onDrawerSlide: "); } //抽屜滑出顯示時觸發 @Override public void onDrawerOpened(View drawerView) { Log.d(TAG, "onDrawerOpened: "); } //抽屜滑入隱藏時觸發 @Override public void onDrawerClosed(View drawerView) { Log.d(TAG, "onDrawerClosed: "); } //狀態改變時觸發,也就是當顯示變為隱藏,或反過來時觸發 @Override public void onDrawerStateChanged(int newState) { Log.d(TAG, "onDrawerStateChanged: "); } });
ActionBarDrawerToggle
這是一個ActionBar(Toolbar)與DrawerLayout之間的觸發器,將DrawerLayout的滑動與Actionbar進行聯動。它在抽屜狀態變化時改變Actionbar上的返回圖示(android.R.id.home),而且帶有動畫效果。
這個類是DrawerLayout.DrawerListener
的子類,它有兩個構造方法,分別對應Actionbar與Toolbar。
- ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, int openDrawerContentDescRes, int closeDrawerContentDescRes)
- ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, Toolbar toolbar, int openDrawerContentDescRes, int closeDrawerContentDescRes)
最後兩個引數是為輔助功能(accessibility)所提供的描述性動作字串。
考慮到它是DrawerLayout.DrawerListener
的子類,當然也是作為監聽器來使用的,在裡面同樣可以複寫監聽DrawerLayout狀態改變的方法。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle mDrawerToggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close){
//再這複寫監聽方法,如果需要的話
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
Log.d(TAG, "onDrawerSlide: ");
}
};
drawer.addDrawerListener(mDrawerToggle);
mDrawerToggle.syncState();
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
//在這裡呼叫同步方法
mDrawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//在視窗配置發生改變的時候同樣要同步toggle的指示器(就是那個圖示)狀態
mDrawerToggle.onConfigurationChanged(newConfig);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 如果mDrawerToggle的onOptionsItemSelected(item)方法返回true,那麼直接返回true
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
// Handle your other action bar items...
return super.onOptionsItemSelected(item);
}
NavigationView
上面說過在DrawerLayout中可以新增任意的View作為抽屜的檢視,但官方還是提供了一個標準的抽屜View,作為抽屜式導航欄(典型的MD風格),它就是NavigationView。
NavigationView繼承於FrameLayout。通常我們看到的是這樣的:
xml如下:
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main" <!--頭部佈局檔案-->
app:menu="@menu/activity_main_drawer"/> <!--選單xml檔案-->
當然也可以通過程式碼的方式動態新增。
新增監聽器
通過呼叫方法:
setNavigationItemSelectedListener(NavigationView.OnNavigationItemSelectedListener listener)
示例:
navigationView.setNavigationItemSelectedListener(new NavigationView
.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
//通過id值與menu檔案中item的id匹配
int id = item.getItemId();
if (id == R.id.nav_camera) {
// Handle the camera action
}
//關閉抽屜
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
});
最後附一個狀態列半透明的方法,此時NavigationView可以延伸到狀態列,這樣效果很好。
//透明狀態列(5.0以上系統支援)
if(Build.VERSION.SDK_INT >= 21){
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
getWindow().setStatusBarColor(Color.TRANSPARENT);
}