Android組件——使用DrawerLayout仿網易新聞v4.4側滑菜單,drawerlayoutv4.4

來源:互聯網
上載者:User

Android組件——使用DrawerLayout仿網易新聞v4.4側滑菜單,drawerlayoutv4.4
轉載請註明出處:http://blog.csdn.net/allen315410/article/details/42914501概述

       今天這篇部落格將記錄一些關於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://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>
       在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 {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {selectItem(position);}}/** * 切換主視圖地區的Fragment *  * @param position */private void selectItem(int position) {// TODO Auto-generated method stubFragment 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將點擊的菜單列表標題傳遞給FragmentFragmentManager 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() {@Overridepublic 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);}}@Overridepublic void onDrawerOpened(android.view.View drawerView) {if (drawerView == mMenuListView) {isDirection_left = true;} else if (drawerView == right_drawer) {isDirection_right = true;}}@Overridepublic 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*/@Overrideprotected void onPostCreate(Bundle savedInstanceState) {super.onPostCreate(savedInstanceState);mMaterialMenuIcon.syncState(savedInstanceState);}/*** 根據onSaveInstanceState回調的狀態,儲存當前icon state*/@Overrideprotected 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>
完成定義操作後,需要載入菜單布局:

/*** 載入菜單*/@Overridepublic 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上菜單*/@Overridepublic 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;@Overrideprotected 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 implementsListView.OnItemClickListener {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position,long id) {selectItem(position);}}/** * DrawerLayout狀態變化監聽 */private class DrawerLayoutStateListener extendsDrawerLayout.SimpleDrawerListener {/** * 當導覽功能表滑動的時候被執行 */@Overridepublic 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);}}/** * 當導覽功能表開啟時執行 */@Overridepublic void onDrawerOpened(android.view.View drawerView) {if (drawerView == mMenuListView) {isDirection_left = true;} else if (drawerView == right_drawer) {isDirection_right = true;}}/** * 當導覽功能表關閉時執行 */@Overridepublic 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將點擊的菜單列表標題傳遞給FragmentFragmentManager fragmentManager = getSupportFragmentManager();fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();// 更新選擇後的item和title,然後關閉菜單mMenuListView.setItemChecked(position, true);setTitle(mMenuTitles[position]);mDrawerLayout.closeDrawer(mMenuListView);}/** * 點擊ActionBar上菜單 */@Overridepublic 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 */@Overrideprotected void onPostCreate(Bundle savedInstanceState) {super.onPostCreate(savedInstanceState);mMaterialMenuIcon.syncState(savedInstanceState);}/** * 根據onSaveInstanceState回調的狀態,儲存當前icon state */@Overrideprotected void onSaveInstanceState(Bundle outState) {mMaterialMenuIcon.onSaveInstanceState(outState);super.onSaveInstanceState(outState);}/** * 載入菜單 */@Overridepublic 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;}}


源碼請在這裡下載


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.