轉載請注明出處: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,用來展示右邊的布局,布局代碼如下:
[html] view
plaincopyprint?
<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_gravity="end"
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之間的切換。
[java] view
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:
case 2:
case 3:
default:
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);
細心的朋友也許會發現“網易新聞”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內建到項目中。下面是我使用的部分代碼:
// 設定抽屜打開時,主要内容區被自定義陰影覆寫
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);
}
}
public void ondraweropened(android.view.view drawerview) {
isdirection_left = true;
isdirection_right = true;
public void ondrawerclosed(android.view.view drawerview) {
isdirection_left = false;
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
protected void onsaveinstancestate(bundle outstate) {
mmaterialmenuicon.onsaveinstancestate(outstate);
super.onsaveinstancestate(outstate);
為了盡量模拟出“網易新聞”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>
完成定義操作後,需要加載菜單布局:
* 加載菜單
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上菜單
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);
case r.id.action_personal:
if (!isdirection_right) {// 右邊欄關閉時,打開
if (showview == mmenulistview) {
mdrawerlayout.opendrawer(right_drawer);
} else {// 右邊欄打開時,關閉
mdrawerlayout.closedrawer(right_drawer);
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;
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 {
public void onitemclick(adapterview<?> parent, view view, int position,
long id) {
selectitem(position);
* drawerlayout狀态變化監聽
private class drawerlayoutstatelistener extends
drawerlayout.simpledrawerlistener {
/**
* 當導航菜單滑動的時候被執行
*/
if (drawerview == mmenulistview) {// 根據isdirection_left決定執行動畫
mmaterialmenuicon.settransformationoffset(
materialmenudrawable.animationstate.burger_arrow,
isdirection_left ? 2 - slideoffset : slideoffset);
} else if (drawerview == right_drawer) {// 根據isdirection_right決定執行動畫
isdirection_right ? 2 - slideoffset : slideoffset);
* 當導航菜單打開時執行
* 當導航菜單關閉時執行
* 切換主視圖區域的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:
case 2:
case 3:
default:
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上菜單
public boolean onoptionsitemselected(menuitem item) {
int id = item.getitemid();
switch (id) {
case android.r.id.home:
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);
case r.id.action_personal:
if (showview == mmenulistview) {
return super.onoptionsitemselected(item);
* 根據onpostcreate回調的狀态,還原對應的icon state
protected void onpostcreate(bundle savedinstancestate) {
super.onpostcreate(savedinstancestate);
mmaterialmenuicon.syncstate(savedinstancestate);
* 根據onsaveinstancestate回調的狀态,儲存目前icon state
protected void onsaveinstancestate(bundle outstate) {
mmaterialmenuicon.onsaveinstancestate(outstate);
super.onsaveinstancestate(outstate);
* 加載菜單
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;
源碼請在這裡下載下傳