天天看點

Android元件——使用DrawerLayout仿網易新聞v4.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抽屜式布局是什麼樣子的。

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

       好,大家已經看到了,網易新聞用戶端效果很明顯,當我們手指在螢幕左側向右滑動時候,就會有一個抽屜式的菜單從左邊彈出,并且是“懸浮”在主界面之上的,合理的利用了裝置上有限的空間,同樣手指在螢幕右側向左滑動也會出現一個向左彈出的抽屜式菜單,使用者體驗效果還是不錯的,在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元件——使用DrawerLayout仿網易新聞v4.4側滑菜單

      下面這個抽屜布局引用的是android.support.v4.drawerlayout,類似于linealayout、relativelayout等布局一樣定義,在drawerlayout内部再定義3個布局,分别是管理主界面的framelayout,此布局用來展示界面切換的fragment,下面是listview,用來展示菜單清單,最後是一個relativelayout,用來展示右邊的布局,布局代碼如下:

[html] view

plaincopyprint?

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

<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裡,如下:

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

<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

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

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中顯示出來,下面是部分代碼:

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

/** 

 * 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內建到項目中。下面是我使用的部分代碼:

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

    // 設定抽屜打開時,主要内容區被自定義陰影覆寫  

    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方法:

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

* 根據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中這樣定義一個:

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

<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>  

完成定義操作後,需要加載菜單布局:

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

* 加載菜單 

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;  

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

* 點選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代碼,注釋也很詳盡,可以友善了解,實在不行,還可以點選部落格下方的下載下傳連結,直接下載下傳源碼運作一下。

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

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;  

源碼請在這裡下載下傳

繼續閱讀