概述
今天這篇博客將記錄一些關於DrawerLayout的基本用法,我想關於DrawerLayout的用法也許有不少不夠了解,這也是比較正常的事情,因爲DrawerLayout作爲Android組件是Google後來在android中添加的,在android.support.v4包下。那麼,DrawerLayout是一個怎麼的組件呢?我們知道,當我們使用Android上各類App的時候,是不是注意過App主頁上通常有一個“側滑菜單”?關於側滑菜單的實現,我在前面博客裏有一些介紹,想多些瞭解的朋友請移步:
Android自定義控件——開源組件SlidingMenu的項目集成
這裏用“網易新聞”客戶端v4.4的截圖來說明一下,這個DrawerLayout抽屜式佈局是什麼樣子的。
好,大家已經看到了,網易新聞客戶端效果很明顯,當我們手指在屏幕左側向右滑動時候,就會有一個抽屜式的菜單從左邊彈出,並且是“懸浮”在主界面之上的,合理的利用了設備上有限的空間,同樣手指在屏幕右側向左滑動也會出現一個向左彈出的抽屜式菜單,用戶體驗效果還是不錯的,在DrawerLayout出現之前,我們需要做側滑菜單時,不得不自己實現一個或者使用Github上的開源的項目SlidingMenu,也許是Google也看到了SlidingMenu的強大之處,於是在Android的後期版本中添加了DrawerLayout來實現SlidingMenu同樣功能的組件,而且爲了兼容早期版本,將其添加在android,support.v4包下。
關於DrawerLayout的Training:http://developer.android.com/training/implementing-navigation/nav-drawer.html
關於DrawerLayout的API:http://developer.android.com/reference/android/support/v4/widget/DrawerLayout.html
另外,我已經翻譯過了Google的Training課程,地址是:http://blog.csdn.net/allen315410/article/details/42875231
效果預覽
創建抽屜佈局
下面這個抽屜佈局引用的是android.support.v4.DrawerLayout,類似於LineaLayout、RelativeLayout等佈局一樣定義,在DrawerLayout內部再定義3個佈局,分別是管理主界面的FrameLayout,此佈局用來展示界面切換的Fragment,下面是ListView,用來展示菜單列表,最後是一個RelativeLayout,用來展示右邊的佈局,佈局代碼如下:
- <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" >
- <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="200dp"
- android:layout_height="match_parent"
- android:layout_gravity="start"
- android:background="#111"
- android:choiceMode="singleChoice"
- android:divider="@android:color/transparent"
- android:dividerHeight="0dp" />
- <RelativeLayout
- android:id="@+id/right_drawer"
- android:layout_width="220dp"
- android:layout_height="match_parent"
- android:layout_gravity="end"
- android:background="#111"
- android:gravity="center_horizontal" >
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="這是右邊欄"
- android:textColor="@android:color/white"
- android:textSize="24sp" />
- </RelativeLayout>
- </android.support.v4.widget.DrawerLayout>
這個佈局文件示範了一些重要的佈局特徵.
- 主要內容的視圖(FrameLayout)必須是DrawLayout的第一個子元素, 因爲導航抽屜是在主要內容視圖的上面.
- 主要內容視圖設置爲匹配父視圖的寬度和高度, 因爲它代表了整個界面導航抽屜是隱藏的.
- 抽屜視圖(ListView)必須指定其水平重力與android:layout_gravity屬性。支持從右到左(RTL)語言,指定值與 "start" 代替 "left"(所以抽屜裏出現在佈局的右側當佈局是RTL時).這裏將ListView設置爲左邊欄菜單,所以android:layout_gravity屬性設置爲“start”,將RelativeLayout設置爲右邊欄,設置android:layout_gravity屬性爲“end”.
- 抽屜視圖指定其寬度用dp單位和高度匹配父視圖。抽屜裏的寬度不能超過320 dp, 所以用戶總是可以看到主要內容視圖的一部分。
初始化抽屜列表
正如上述所講,因爲DrawerLayout裏包含一個ListView作爲左邊欄側滑菜單,所以我們需要首先初始化這個抽屜列表,並且爲這個列表適配上數據,數據適配器使用的是最簡單的ArrayAdapter,模擬數據被簡單的定義在res/values/strings.xml裏,如下:
- <string-array name="menu_array">
- <item>Menu 1</item>
- <item>Menu 2</item>
- <item>Menu 3</item>
- <item>Menu 4</item>
- </string-array>
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- ......
- // 初始化菜單列表
- mMenuTitles = getResources().getStringArray(R.array.menu_array);
- mMenuListView.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, mMenuTitles));
- mMenuListView.setOnItemClickListener(new DrawerItemClickListener());
- ......
- }
處理導航點擊事件
當用戶選擇了抽屜列表裏面的一個Item時, 系統調用onItemClickListener上的onItemClick(), 給setOnItemClickListener()你在onItemClick()方法裏面做什麼,在下面的例子中, 選擇每一個Item都會在主要內容的佈局中插入一個不同的Fragment.並且將導航列表的內容傳遞給Fragment中顯示出來,下面是部分代碼:
- /**
- * ListView上的Item點擊事件
- *
- */
- private class DrawerItemClickListener implements ListView.OnItemClickListener {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- selectItem(position);
- }
- }
- /**
- * 切換主視圖區域的Fragment
- *
- * @param position
- */
- private void selectItem(int position) {
- // TODO Auto-generated method stub
- Fragment fragment = new ContentFragment();
- Bundle args = new Bundle();
- switch (position) {
- case 0:
- args.putString("key", mMenuTitles[position]);
- break;
- case 1:
- args.putString("key", mMenuTitles[position]);
- break;
- case 2:
- args.putString("key", mMenuTitles[position]);
- break;
- case 3:
- args.putString("key", mMenuTitles[position]);
- break;
- default:
- break;
- }
- fragment.setArguments(args); // FragmentActivity將點擊的菜單列表標題傳遞給Fragment
- FragmentManager fragmentManager = getSupportFragmentManager();
- fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
- // 更新選擇後的item和title,然後關閉菜單
- mMenuListView.setItemChecked(position, true);
- setTitle(mMenuTitles[position]);
- mDrawerLayout.closeDrawer(mMenuListView);
- }
開源material-menu的集成
細心的朋友也許會發現“網易新聞”v4.4客戶端主頁左上角上有個菜單“動態”的菜單按鈕,顯示流程是這樣的,當菜單沒有打開時,顯示“三”這樣的三條橫線,當菜單打開(無論左右菜單)時,會顯示“<-”這樣的按鈕,不停的變化,這樣的效果是不是有點絢麗啊?!瞭解過Android5.0的朋友,應該會知道這種效果是使用了Android5.0新推出的Material Design設計語言做出來的效果,那麼該怎麼模仿這個效果呢?不好意思,由於偷懶,我已經在牛牛的Github中找到了這樣的效果——material-menu組件,該組件模擬出了Android5.0下的Material Design效果,注意的是該組件中使用了JackWharton的NineOldAndroids動畫效果。
material-menu主頁:https://github.com/balysv/material-menu
NineOldAndroids主頁:https://github.com/JakeWharton/NineOldAndroids
關於material-menu的使用可以參考其主頁上的Demo和說明,集成時需要下載NineOldAndroids導出jar集成到項目中。下面是我使用的部分代碼:
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- ......
- // 設置抽屜打開時,主要內容區被自定義陰影覆蓋
- mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
- // 設置ActionBar可見,並且切換菜單和內容視圖
- getActionBar().setDisplayHomeAsUpEnabled(true);
- getActionBar().setHomeButtonEnabled(true);
- mMaterialMenuIcon = new MaterialMenuIcon(this, Color.WHITE, Stroke.THIN);
- mDrawerLayout.setDrawerListener(new DrawerLayout.SimpleDrawerListener() {
- @Override
- public void onDrawerSlide(View drawerView, float slideOffset) {
- showView = drawerView;
- if (drawerView == mMenuListView) {
- mMaterialMenuIcon.setTransformationOffset(MaterialMenuDrawable.AnimationState.BURGER_ARROW, isDirection_left ? 2 - slideOffset : slideOffset);
- } else if (drawerView == right_drawer) {
- mMaterialMenuIcon.setTransformationOffset(MaterialMenuDrawable.AnimationState.BURGER_ARROW, isDirection_right ? 2 - slideOffset : slideOffset);
- }
- }
- @Override
- public void onDrawerOpened(android.view.View drawerView) {
- if (drawerView == mMenuListView) {
- isDirection_left = true;
- } else if (drawerView == right_drawer) {
- isDirection_right = true;
- }
- }
- @Override
- public void onDrawerClosed(android.view.View drawerView) {
- if (drawerView == mMenuListView) {
- isDirection_left = false;
- } else if (drawerView == right_drawer) {
- isDirection_right = false;
- showView = mMenuListView;
- }
- }
- });
- ......
- }
- /**
- * 根據onPostCreate回調的狀態,還原對應的icon state
- */
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- mMaterialMenuIcon.syncState(savedInstanceState);
- }
- /**
- * 根據onSaveInstanceState回調的狀態,保存當前icon state
- */
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- mMaterialMenuIcon.onSaveInstanceState(outState);
- super.onSaveInstanceState(outState);
- }
添加ActionBar上的菜單按鈕
爲了儘量模擬出“網易新聞”v4.4客戶端主頁,我也在標題欄右上角添加一個小圖標,爲了能在點擊這個小圖標的時候彈出右邊欄菜單,實現方式很簡單,關於ActionBar上添加導航的知識可以在csdn上搜到一些解釋或者上Android開發者官網查看源文檔,我這裏首先簡單的在res/menu下main.xml中這樣定義一個:
- <menu xmlns:android="http://schemas.android.com/apk/res/android" >
- <item
- android:id="@+id/action_personal"
- android:icon="@drawable/action_personal"
- android:orderInCategory="100"
- android:showAsAction="always"
- android:title="@string/action_personal"/>
- </menu>
- /**
- * 加載菜單
- */
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
標題欄導航點擊事件處理
- /**
- * 點擊ActionBar上菜單
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int id = item.getItemId();
- switch (id) {
- case android.R.id.home:
- if (showView == mMenuListView) {
- if (!isDirection_left) { // 左邊欄菜單關閉時,打開
- mDrawerLayout.openDrawer(mMenuListView);
- } else {// 左邊欄菜單打開時,關閉
- mDrawerLayout.closeDrawer(mMenuListView);
- }
- } else if (showView == right_drawer) {
- if (!isDirection_right) {// 右邊欄關閉時,打開
- mDrawerLayout.openDrawer(right_drawer);
- } else {// 右邊欄打開時,關閉
- mDrawerLayout.closeDrawer(right_drawer);
- }
- }
- break;
- case R.id.action_personal:
- if (!isDirection_right) {// 右邊欄關閉時,打開
- if (showView == mMenuListView) {
- mDrawerLayout.closeDrawer(mMenuListView);
- }
- mDrawerLayout.openDrawer(right_drawer);
- } else {// 右邊欄打開時,關閉
- mDrawerLayout.closeDrawer(right_drawer);
- }
- break;
- default:
- break;
- }
- return super.onOptionsItemSelected(item);
- }
同樣的道理,如果當前即將顯示或者隱藏的是右邊導航菜單的話,我們需要進一步判斷右邊導航是否已經顯示,從而進行相關打開或隱藏的決定。
這裏的邏輯似乎解釋的有點亂,而且代碼是分片段貼出來的,不利於理解,需要進一步理解的話,不妨繼續看下面的部分,我已經貼出了所以的Java代碼,註釋也很詳盡,可以方便理解,實在不行,還可以點擊博客下方的下載鏈接,直接下載源碼運行一下。
全部源碼
- public class MainActivity extends FragmentActivity {
- /** DrawerLayout */
- private DrawerLayout mDrawerLayout;
- /** 左邊欄菜單 */
- private ListView mMenuListView;
- /** 右邊欄 */
- private RelativeLayout right_drawer;
- /** 菜單列表 */
- private String[] mMenuTitles;
- /** Material Design風格 */
- private MaterialMenuIcon mMaterialMenuIcon;
- /** 菜單打開/關閉狀態 */
- private boolean isDirection_left = false;
- /** 右邊欄打開/關閉狀態 */
- private boolean isDirection_right = false;
- private View showView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
- mMenuListView = (ListView) findViewById(R.id.left_drawer);
- right_drawer = (RelativeLayout) findViewById(R.id.right_drawer);
- this.showView = mMenuListView;
- // 初始化菜單列表
- mMenuTitles = getResources().getStringArray(R.array.menu_array);
- mMenuListView.setAdapter(new ArrayAdapter<String>(this,
- R.layout.drawer_list_item, mMenuTitles));
- mMenuListView.setOnItemClickListener(new DrawerItemClickListener());
- // 設置抽屜打開時,主要內容區被自定義陰影覆蓋
- mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow,
- GravityCompat.START);
- // 設置ActionBar可見,並且切換菜單和內容視圖
- getActionBar().setDisplayHomeAsUpEnabled(true);
- getActionBar().setHomeButtonEnabled(true);
- mMaterialMenuIcon = new MaterialMenuIcon(this, Color.WHITE, Stroke.THIN);
- mDrawerLayout.setDrawerListener(new DrawerLayoutStateListener());
- if (savedInstanceState == null) {
- selectItem(0);
- }
- }
- /**
- * ListView上的Item點擊事件
- *
- */
- private class DrawerItemClickListener implements
- ListView.OnItemClickListener {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position,
- long id) {
- selectItem(position);
- }
- }
- /**
- * DrawerLayout狀態變化監聽
- */
- private class DrawerLayoutStateListener extends
- DrawerLayout.SimpleDrawerListener {
- /**
- * 當導航菜單滑動的時候被執行
- */
- @Override
- public void onDrawerSlide(View drawerView, float slideOffset) {
- showView = drawerView;
- if (drawerView == mMenuListView) {// 根據isDirection_left決定執行動畫
- mMaterialMenuIcon.setTransformationOffset(
- MaterialMenuDrawable.AnimationState.BURGER_ARROW,
- isDirection_left ? 2 - slideOffset : slideOffset);
- } else if (drawerView == right_drawer) {// 根據isDirection_right決定執行動畫
- mMaterialMenuIcon.setTransformationOffset(
- MaterialMenuDrawable.AnimationState.BURGER_ARROW,
- isDirection_right ? 2 - slideOffset : slideOffset);
- }
- }
- /**
- * 當導航菜單打開時執行
- */
- @Override
- public void onDrawerOpened(android.view.View drawerView) {
- if (drawerView == mMenuListView) {
- isDirection_left = true;
- } else if (drawerView == right_drawer) {
- isDirection_right = true;
- }
- }
- /**
- * 當導航菜單關閉時執行
- */
- @Override
- public void onDrawerClosed(android.view.View drawerView) {
- if (drawerView == mMenuListView) {
- isDirection_left = false;
- } else if (drawerView == right_drawer) {
- isDirection_right = false;
- showView = mMenuListView;
- }
- }
- }
- /**
- * 切換主視圖區域的Fragment
- *
- * @param position
- */
- private void selectItem(int position) {
- Fragment fragment = new ContentFragment();
- Bundle args = new Bundle();
- switch (position) {
- case 0:
- args.putString("key", mMenuTitles[position]);
- break;
- case 1:
- args.putString("key", mMenuTitles[position]);
- break;
- case 2:
- args.putString("key", mMenuTitles[position]);
- break;
- case 3:
- args.putString("key", mMenuTitles[position]);
- break;
- default:
- break;
- }
- fragment.setArguments(args); // FragmentActivity將點擊的菜單列表標題傳遞給Fragment
- FragmentManager fragmentManager = getSupportFragmentManager();
- fragmentManager.beginTransaction()
- .replace(R.id.content_frame, fragment).commit();
- // 更新選擇後的item和title,然後關閉菜單
- mMenuListView.setItemChecked(position, true);
- setTitle(mMenuTitles[position]);
- mDrawerLayout.closeDrawer(mMenuListView);
- }
- /**
- * 點擊ActionBar上菜單
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int id = item.getItemId();
- switch (id) {
- case android.R.id.home:
- if (showView == mMenuListView) {
- if (!isDirection_left) { // 左邊欄菜單關閉時,打開
- mDrawerLayout.openDrawer(mMenuListView);
- } else {// 左邊欄菜單打開時,關閉
- mDrawerLayout.closeDrawer(mMenuListView);
- }
- } else if (showView == right_drawer) {
- if (!isDirection_right) {// 右邊欄關閉時,打開
- mDrawerLayout.openDrawer(right_drawer);
- } else {// 右邊欄打開時,關閉
- mDrawerLayout.closeDrawer(right_drawer);
- }
- }
- break;
- case R.id.action_personal:
- if (!isDirection_right) {// 右邊欄關閉時,打開
- if (showView == mMenuListView) {
- mDrawerLayout.closeDrawer(mMenuListView);
- }
- mDrawerLayout.openDrawer(right_drawer);
- } else {// 右邊欄打開時,關閉
- mDrawerLayout.closeDrawer(right_drawer);
- }
- break;
- default:
- break;
- }
- return super.onOptionsItemSelected(item);
- }
- /**
- * 根據onPostCreate回調的狀態,還原對應的icon state
- */
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- mMaterialMenuIcon.syncState(savedInstanceState);
- }
- /**
- * 根據onSaveInstanceState回調的狀態,保存當前icon state
- */
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- mMaterialMenuIcon.onSaveInstanceState(outState);
- super.onSaveInstanceState(outState);
- }
- /**
- * 加載菜單
- */
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
- }