Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android組件DrawerLayout仿網易新聞v4.4側滑菜單

Android組件DrawerLayout仿網易新聞v4.4側滑菜單

編輯:關於Android編程

概述 

      今天這篇博客將記錄一些關於DrawerLayout的基本用法,我想關於DrawerLayout的用法也許有不少不夠了解,這也是比較正常的事情,因為DrawerLayout作為Android組件是Google後來在android中添加的,在android.support.v4包下。那麼,DrawerLayout是一個怎麼的組件呢?我們知道,當我們使用Android上各類App的時候,是不是注意過App主頁上通常有一個“側滑菜單”?關於側滑菜單的實現,我在前面博客裡有一些介紹,想多些了解的朋友請移步:

Android自定義控件——側滑菜單
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://www.jb51.net/article/102142.htm

效果預覽


創建抽屜布局

      下面這個抽屜布局引用的是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> 

       在Java代碼中,首先創建一個MainActivity繼承了android.support.v4.app.FragmentActivity,因為後續中需要進行Fragment之間的切換。

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; 
 } 
 } 
 }); 
 ...... 
 
} 

此外,還需要關聯一下meterial-menu的狀態,需要覆蓋Activity下的onPostCreate和onSaveInstanceState方法:

/** 
* 根據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); 
} 

      這段的邏輯有點繞,事實上我做的是這樣的,需要保證主界面上只能最多顯示一個菜單布局,當左邊的菜單布局展示時,此時打開右邊菜單布局時,需要隱藏左邊菜單布局;同樣,如果右邊的菜單布局已經在展示的時候,這時需要打開左邊菜單布局,必須首先隱藏掉右邊的菜單布局。為了判斷當前即將顯示或者關閉的是哪個布局,我在全局變量中定義了showView用來標記當前即將顯示或者關閉的視圖,如果showView==mMenuListView,說明左邊菜單布局是即將被顯示或隱藏的,這時進一步判斷菜單是視圖mMenuListView的是否已經顯示的標記isDirection_left,來打開或者關閉左邊視圖菜單。
      同樣的道理,如果當前即將顯示或者隱藏的是右邊導航菜單的話,我們需要進一步判斷右邊導航是否已經顯示,從而進行相關打開或隱藏的決定。
      這裡的邏輯似乎解釋的有點亂,而且代碼是分片段貼出來的,不利於理解,需要進一步理解的話,不妨繼續看下面的部分,我已經貼出了所以的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; 
 } 
 
} 

源碼請在這裡下載:http://xiazai.jb51.net/201701/yuanma/AndroidDrawerLayout(jb51.net).rar

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved