天天看点

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;  

源码请在这里下载

继续阅读