侧滑菜单是Android应用中出现的比较多的功能,下面来看一下是怎么实现的。

Android Studio里面有现成的模板,给项目取名TestSlidePage,可以看一下它生成的东西

项目目录

从MainActivity里面看一下主要的布局文件setContentView(R.layout.activity_main);是activity_main。

而activity_main.xml里面的内容如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:fitsSystemWindows="true" tools:openDrawer="start">

    <include layout="@layout/app_bar_main" android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <android.support.design.widget.NavigationView android:id="@+id/nav_view"
        android:layout_width="wrap_content" android:layout_height="match_parent"
        android:layout_gravity="start" android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main" app:menu="@menu/activity_main_drawer" />

</android.support.v4.widget.DrawerLayout>

整体是一个DrawerLayout,它里面是两个view,第一个是你的主要布局内容,第二个是侧滑出来的布局内容。这样就可以实现抽屉效果。

动态效果

此处侧滑出来的控件是NavigationView,是用于导航,其实也可以是别的view,比如说listview等。

下面说一下NavigationView

<android.support.design.widget.NavigationView android:id="@+id/nav_view"
        android:layout_width="wrap_content" android:layout_height="match_parent"
        android:layout_gravity="start" android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main" app:menu="@menu/activity_main_drawer" />

看到它里面有属性app:headerLayout="@layout/nav_header_main"设置了导航的头部布局(就是图中的绿色部分),看一下nav_header_main.xml这个布局文件,内容也确实是一个图片带两个文字

导航头部

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="@dimen/nav_header_height"
    android:background="@drawable/side_nav_bar"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:theme="@style/ThemeOverlay.AppCompat.Dark" android:orientation="vertical"
    android:gravity="bottom">

    <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:paddingTop="@dimen/nav_header_vertical_spacing"
        android:src="@android:drawable/sym_def_app_icon" android:id="@+id/imageView" />

    <TextView android:layout_width="match_parent" android:layout_height="wrap_content"
        android:paddingTop="@dimen/nav_header_vertical_spacing" android:text="Android Studio"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="android.studio@android.com" android:id="@+id/textView" />

</LinearLayout>

那么导航头部下面的这些东西是怎么来的呢?

原来是app:menu="@menu/activity_main_drawer"指定了导航里面的菜单,可以看到这个菜单文件里面的内容是下面这样的:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:checkableBehavior="single">
        <item android:id="@+id/nav_camara" android:icon="@android:drawable/ic_menu_camera"
            android:title="Import" />
        <item android:id="@+id/nav_gallery" android:icon="@android:drawable/ic_menu_gallery"
            android:title="Gallery" />
        <item android:id="@+id/nav_slideshow" android:icon="@android:drawable/ic_menu_slideshow"
            android:title="Slideshow" />
        <item android:id="@+id/nav_manage" android:icon="@android:drawable/ic_menu_manage"
            android:title="Tools" />
    </group>

    <item android:title="Communicate">
        <menu>
            <item android:id="@+id/nav_share" android:icon="@android:drawable/ic_menu_share"
                android:title="Share" />
            <item android:id="@+id/nav_send" android:icon="@android:drawable/ic_menu_send"
                android:title="Send" />
        </menu>
    </item>
</menu>

菜单分为两个组,所以图上会有组与组之间的分隔线。菜单的item里面android:icon用来设置菜单的图标,android:title用来设置菜单的文字。当菜单项很多的时候,侧滑页面是可以上下滑动的。


假如说,我的菜单很少,只有两个,所以怎么都不可能会出现滑动的情况,而且我还希望在侧滑页面底部有一个类似“退出登录”的按钮,那怎么办?

如果把“退出登录”作为一个菜单项,那么它就会和其他菜单项靠得很近,达不到我想要的位于侧滑页底部的效果。

那么可以用添加子视图的方法解决。将“退出登录”按钮作为子视图加到NavigationView上。

之前进过一个坑,就是使用LinearLayout布局,无论怎么设置边距、位置等,按钮始终还是在NavigationView上方,o(╯□╰)o,o(╯□╰)o,o(╯□╰)o,o(╯□╰)o


LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);

Button bt_logOut = new Button(this);
bt_logOut.setText(R.string.home_slide_logout);

LinearLayout layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.BOTTOM;
layoutParams.gravity = Gravity.BOTTOM;
layoutParams.bottomMargin = 20;

linearLayout.addView(bt_logOut);

navigationView.addView(linearLayout,layoutParams);

linearlayout on navigationview

后来在stack overflow上找到了答案

NavigationView是基于FrameLayout的

原来NavigationView是基于FrameLayout的,所以不应该用LinearLayout,而是应该是FrameLayout


 FrameLayout frameLayout = new FrameLayout(this);

 this.addLogOutButtonToNaviView();

 Button bt_logOut = new Button(this);
 bt_logOut.setText(R.string.home_slide_logout);
 bt_logOut.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View v) {

     }
 });

FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.BOTTOM;
layoutParams.bottomMargin = 20;
layoutParams.leftMargin = 20;
layoutParams.rightMargin = 20;

frameLayout.addView(bt_logOut);

navigationView.addView(frameLayout,layoutParams);

这样就达到了将“退出登录”按钮放在侧滑页底部的效果

退出登录在侧滑页底部


最最重要的,有了主页,有了侧滑页,还要有滑动的过程。

使用ActionBarDrawerToggle

 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

 if (getSupportActionBar() != null){
     getSupportActionBar().setHomeButtonEnabled(true);//设置返回键可用(三杠)
     getSupportActionBar().setDisplayHomeAsUpEnabled(true);//显示后退位置的图标
 }

drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer,toolbar, R.string.home_slide_open, R.string.home_slide_close);
drawer.setDrawerListener(toggle);
toggle.syncState();

当按下导航栏上的那个三杠按钮的时候,执行抽屉的开和关的操作

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   int id = item.getItemId();

   //如果按下的是home位置按钮(三杠或者后退)
   if (id == android.R.id.home){
       if(drawer.isDrawerOpen(GravityCompat.START)){
           drawer.closeDrawers();//收起侧面菜单
       }else{
           drawer.openDrawer(GravityCompat.START);//弹出侧面菜单
       }
       return true;//返回true表示此菜单的事件已经完成,不需要再传递给别人
   }

   return super.onOptionsItemSelected(item);
}


NavigationView里面的几个菜单,虽然我给它们设置了图标,但是却没在界面上显示!!!!

后来我把图片改成24dp的,就能显示了----------具体原因还待看一看


当选中导航栏的某个菜单的时候,对其进行监听,可以去执行相应的操作

navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
	  @Override
	  public boolean onNavigationItemSelected(MenuItem item) {	      
	      int id = item.getItemId();
	      switch (id) {
	          case xxx: {
	              Intent intent = new Intent(HomeActivity.this, XXX.class);
	              startActivity(intent);
	          }
	          break;
	          case yyy: {
	              Intent intent = new Intent(HomeActivity.this, YYY.class);
	              startActivity(intent);
	          }
	          break;
	          default:
	              break;
	      }
	      return true;
	  }
	});

在navigationview上设置navigationItemSelectedListener,可以获取选中的那个菜单。

但是上方的代码会有一个小小的问题,选中了某个菜单之后,它就是选中的状态,放开后也还是选中状态。而我的需求是选中后就恢复原状。

可以在onNavigationItemSelected被触发的时候,在里面添加一行代码item.setCheckable(false);这样就能在选中后恢复原状。

【吐槽:我本来以为是item.setChecked(false);这个方法可以让被选中的恢复原状,但它没效果。%>_<%】


后来我发现一个问题,menu左边的图标,不管图片是什么颜色,显示出来的时候都是灰色的【%>_<%怎么回事!】

原来navigationview有以下两个属性,分别可以设置图标的颜色和menu的字体颜色

app:itemIconTint="@color/color_slide_navi_menu_item_icon_tint"
app:itemTextColor="@color/color_slide_navi_menu_item_text"

app:itemIconTint用来设置图标的颜色
app:itemTextColor用来设置菜单字体的颜色