借鉴以下博文
https://www.jianshu.com/p/d2b1689a23bf
https://blog.csdn.net/lmj623565791/article/details/46405409
DrawerLayout
简述DrawerLayout
- DrawerLayout是官方提供给我们的一个侧滑菜单控件,说到侧滑,相信很多人都用过github上的SlidingMenu,不过好像有两个版本,一个是单独的,另一个需要依赖另一个开源项目:ActionBarSherlock;
- 在Material Design设计规范中,随处可见的很多侧滑菜单的动画效果,大都可以通过Toolbar + DrawerLayout来实现。
- 另外还有人喜欢称DrawerLayout为抽屉控件。
注意事项
- 主内容视图一定要是DrawerLayout的第一个子视图
- 主内容视图的宽度和高度需要match_parent
- 必须显示指定侧滑视图的
android:layout_gravity属性
,当android:layout_gravity="start"
时,从左向右滑出菜单,当android:layout_gravity="end"
时,从右向左滑出菜单。另外,不推荐使用left和right! - 侧滑视图的宽度以dp为单位,不建议超过320dp(为了总能看到一些主内容视图),这里我习惯选择wrap_content。
- 可以结合ActionBar使用当用户点击ActionBar上的应用图标,弹出侧滑菜单!当然这里我们讨论的是Toolbar!
DrawerLayout的基本使用
- DrawerLayout的用法比较简单,如下所示。
AndroidManifest注册文件
- 由于涉及到Toolbar,因此需要首先设置Activity主题为NoActionBar
- 如下
- 再来看自定义主题,主要是可以改变colorPrimary、colorAccent常用背景颜色。
- 其实不用这么麻烦,完全可以采用系统自带的NoActionBar,比如
Xml文件
- 直接将DrawerLayout作为根布局,然后其内部的第一个View作为主内容区域,第二个View作为左侧侧滑菜单,第三个View作为右侧侧滑菜单,当前第三个是可选的。
- 主内容区域的宽和好都要是match_parent,理所当然。
- 第二三个View的高度设为match_parent,宽度为固定值,即侧滑菜单的宽度。
- 左侧侧滑(从左向右):
android:layout_gravity="start"
- 右侧侧滑(从右向左):
android:layout_gravity="end"
- 注意位置,不能在第二个View的位置,设置属性为
android:layout_gravity="end"
,就会报错。 - 代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- 主视图 :包括Toolbar标题栏 + 标题栏下内容区域-->
<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<!-- 左侧侧滑菜单:采用NavigationView控件,包括头部界面+菜单界面 -->
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/nav_menu"/>
</androidx.drawerlayout.widget.DrawerLayout>
- 可以看到我们的主内容视图引用的自定义的布局文件app_bar_main,如下所示:里面的组合是:CoordinatorLayout + AppBarLayout + Toolbar,可以参考之前的博文AppbarLayout。
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/nav_header_back"
app:title="DrawerLayout" />
</com.google.android.material.appbar.AppBarLayout>
<include
layout="@layout/app_main"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
- 再看我们的侧滑菜单,用的是NavigationView控件
-
NavigationView控件有两个app属性,分别为
app:headerLayout
和app:menu
,其中headerLayout用于显示头部的布局(可选),menu用于显示建立MenuItem选项的菜单。 -
其中头部布局文件就是nav_header_main,正常的layout布局文件。
-
菜单文件是nav_menu_main,其中menu可以分组,
android:checkableBehavior="single"
可以设置改组为单选。
-
<?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_camera"
android:icon="@drawable/upload"
android:title="重置座位列表"/>
<item
android:id="@+id/nav_gallery"
android:icon="@drawable/chaxun"
android:title="更新座位状态"/>
<item
android:id="@+id/nav_slideshow"
android:icon="@drawable/addlist"
android:title="更新授权名单"/>
<item
android:id="@+id/nav_manage"
android:icon="@drawable/quited"
android:title="退出当前账户"/>
</group>
<item android:title="管理员操作模式">
<menu>
<item
android:id="@+id/nav_share"
android:icon="@drawable/admin"
android:title="管理员登录"/>
<item
android:id="@+id/nav_send"
android:icon="@drawable/ic_menu_manage"
android:title="管理员设置"/>
</menu>
</item>
</menu>
java代码
package com.example.drawerlayouttest;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.Toast;
import com.google.android.material.navigation.NavigationView;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener{
private DrawerLayout drawer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/******************Toolbar处理部分*******************************/
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
//Toolbar中增加ActionBar的特性:Action按钮和overflow按钮
setSupportActionBar(toolbar);
/******************DrawerLayout处理部分*******************************/
// 带Home旋转开关按钮,点击按钮显示或隐藏侧滑菜单。
drawer = (DrawerLayout) findViewById(R.id.drawer);
// 绑定DrawerLayout和Toolbar,使得点击Toolbar的导航图标(Home旋转开关按钮),可以弹出或隐藏DrawerLayout
ActionBarDrawerToggle actionBarDrawerToggle = new ActionBarDrawerToggle(this,drawer,toolbar,R.string.nav_drawer_open,R.string.nav_drawer_close);
// 将图标放入到Toolbar上。
actionBarDrawerToggle.syncState();
// 设置DrawerLayout的监听事件
// 监听方法是isDrawerOpen(GravityCompat.START),对应的打开和关闭方法是openDrawer(GravityCompat.START)/closeDrawer(GravityCompat.START)
//drawer.setDrawerListener(actionBarDrawerToggle);//setDrawerListener已过时,系统不建议使用,建议使用addDrawerListener
drawer.addDrawerListener(actionBarDrawerToggle);
/******************NavigationView处理部分*******************************/
NavigationView nav = (NavigationView)findViewById(R.id.nav);
// 设置NavigationView菜单项的监听事件
nav.setNavigationItemSelectedListener(this);
}
// NavigationView菜单项的反应动作
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch(item.getItemId()){
case R.id.nav_upload_seatlist:
Toast.makeText(this,"You clicked 重置座位列表",Toast.LENGTH_SHORT).show();
break;
case R.id.nav_upload_seatstatus:
Toast.makeText(this,"You clicked 更新座位列表",Toast.LENGTH_SHORT).show();
break;
case R.id.nav_upload_authorlist:
Toast.makeText(this,"You clicked 更新授权名单",Toast.LENGTH_SHORT).show();
break;
case R.id.nav_quit_CurrentAccount:
Toast.makeText(this,"You clicked 退出当前账户",Toast.LENGTH_SHORT).show();
break;
case R.id.nav_admin_login:
Toast.makeText(this,"You clicked 管理员登录",Toast.LENGTH_SHORT).show();
break;
case R.id.nav_admin_set:
Toast.makeText(this,"You clicked 管理员设置",Toast.LENGTH_LONG).show();
break;
default:
break;
}
return true;
}
/**
* 按下安卓机的Back键时,会调用onBackPressed()方法
* 判断DrawerLayout是否为打开状态,如果已经打开,则关闭,否则就继续等待。
*/
@Override
public void onBackPressed() {
if(drawer.isDrawerOpen(GravityCompat.START)){
drawer.closeDrawer(GravityCompat.START);
}else
{
super.onBackPressed();
//drawer.openDrawer(GravityCompat.START);
}
}
}
效果展示
选中NavigationView的Sub items
- 看标题的时候可能有些迷糊
- 先看下没改之前的效果:
- 下面的管理员操作模式就是Sub items
- 可以看到Sub items没法被选中,就是没法变成灰色。
- 参考鸿洋大神博文,做以下修改。
- 在NavigationView的菜单文件中,去除group的android:checkableBehavior=“single”,每个item下面增加android:checkable=“true”
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group>
<item
android:id="@+id/nav_upload_seatlist"
android:checkable="true"
android:icon="@drawable/upload"
android:title="重置座位列表" />
<item
android:id="@+id/nav_upload_seatstatus"
android:checkable="true"
android:icon="@drawable/chaxun"
android:title="更新座位状态" />
<item
android:id="@+id/nav_upload_authorlist"
android:checkable="true"
android:icon="@drawable/addlist"
android:title="更新授权名单" />
<item
android:id="@+id/nav_quit_CurrentAccount"
android:checkable="true"
android:icon="@drawable/quited"
android:title="退出当前账户" />
</group>
<item android:title="Sub items">
<menu>
<item
android:id="@+id/nav_admin_login"
android:checkable="true"
android:icon="@drawable/admin"
android:title="管理员登录" />
<item
android:id="@+id/nav_admin_set"
android:checkable="true"
android:icon="@drawable/ic_menu_manage"
android:title="管理员设置" />
</menu>
</item>
</menu>
- java代码中手动切换按钮的选中状态
// NavigationView菜单项的反应动作
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
/*
* 保证能选中Sub items的代码
* 思路:保存MenuItem的上一个值
* 如果上一个传进来的按钮存在,就关闭它的选中状态
* 打开新传进来的按钮的选中状态
* mPr更新eMenuItem
* */
if(mPreMenuItem != null){
mPreMenuItem.setChecked(false);
}
item.setChecked(true);
mPreMenuItem = item;
//drawer.closeDrawers();//关闭侧滑菜单,在这里相当于drawer.openDrawer(GravityCompat.START);
switch(item.getItemId()){
...
}
return true;
}
- 修改效果如下所示:
NavigaionView在Toolbar下方
- Xml布局中,DrawerLayout在Toolbar的下方。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/nav_header_back"
app:title="DrawerLayout"/>
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hi DrawerLayout!"
android:textSize="25sp"/>
</FrameLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/nav_menu"/>
</androidx.drawerlayout.widget.DrawerLayout>
</LinearLayout>
- java代码不变
- 效果图如下所示
Toolbar上不显示Home旋转开关按钮
- 上图可以看到我们点击Home旋转开关按钮,显示和隐藏了侧滑菜单,南无如果我们想要不通过按钮点击,只能右划出菜单需要怎么做呢
- 我们先看下带Home旋转开关按钮的代码是如何写的:
drawer = (DrawerLayout) findViewById(R.id.drawer);
ActionBarDrawerToggle actionBarDrawerToggle = new ActionBarDrawerToggle(this,drawer,toolbar,R.string.nav_drawer_open,R.string.nav_drawer_close);
actionBarDrawerToggle.syncState();
drawer.addDrawerListener(actionBarDrawerToggle);
- 这个Home旋转开关按钮实际上是通过ActionBarDrawerToggle代码绑定到Toolbar上的,ActionBarDrawerToggle是和DrawerLayout搭配使用的,它可以改变android.R.id.home返回图标,监听drawer的显示和隐藏。
- ActionBarDrawerToggle的syncState方法会和Toolbar关联,将图标放入到Toolbar上。
- 进入ActionBarDrawerToggle构造器可以看到一个不传Toolbar参数的构造器
public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout,
@StringRes int openDrawerContentDescRes,
@StringRes int closeDrawerContentDescRes) {
this(activity, null, drawerLayout, null, openDrawerContentDescRes,
closeDrawerContentDescRes);
}
- 那么不带Home旋转开关按钮的代码如下:
drawer = (DrawerLayout) findViewById(R.id.drawer);
ActionBarDrawerToggle actionBarDrawerToggle = new ActionBarDrawerToggle(this,drawer,R.string.nav_drawer_open,R.string.nav_drawer_close);
drawer.addDrawerListener(actionBarDrawerToggle);
- 效果图如下所示
Xml文件中报错
- 首先看下报错信息:
- 大意是在现存的主题中没有这个id 0x7f15febf的,但是我们却使用这个了。
- 回想整个项目中就开始的时候自定义了一个主题,可能那里出错了,我们将自定义主题
android:theme="@style/AppTheme.NoActionBar"
换成系统主题android:theme="@style/Theme.AppCompat.NoActionBar"
- 然后我们在xml文件中点击错误信息中的refresh按钮
- 发现错误信息解决了,只剩下警告信息,这里不用管警告信息(主要是字符串写的方式不符合语法)。
- 但这时,我们还是想保留自定义主题,怎么办呢?
- 这时我们定位到themes.xml文件,发现我们的自定义主题是在系统自带的下面接着写的,没有覆盖原来的。
- 如果我们把系统自带的Theme注释掉,会怎么样呢?
- 点击错误信息中的refresh按钮,发现错误也同样解决了。
- 所以这里猜测,如果不覆盖系统自带的Theme定义语句,id不会被系统识别。
主内容视图上添加菜单文件
- 直接添加就行
- main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/user"
android:title="用户"
android:icon="@drawable/user"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_settings"
android:title="Settings"
app:showAsAction="never" />
</menu>
- java代码
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch(item.getItemId()){
case R.id.user:
Toast.makeText(this,"You clicked 用户",Toast.LENGTH_SHORT).show();
break;
case R.id.action_settings:
Toast.makeText(this,"You clicked Settings",Toast.LENGTH_SHORT).show();
break;
default:
break;
}
return true;
}
不使用NavigationView,使用DrawerLayout + 其他布局
- App实际开发中,往往不能完全按照Materialdesign的规则来,如网易云音乐的侧滑,底部还有两个按钮。这时候我们可以通过+其他布局来实现特殊的侧滑布局。
- 参考上面引用的博主博文,实现DrawerLayout包裹了一个FrameLayout和一个RelativeLayout,FrameLayout是我们的显示内容区域,RelativeLayout是我们的侧边栏布局。
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer2"
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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="16dp"
android:text="+其他布局来实现特殊的侧滑布局,这里FrameLayout是我们的显示内容区域,RelativeLayout是我们的侧边栏布局。"/>
</FrameLayout>
<RelativeLayout
android:id="@+id/nav2"
android:layout_width="176dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@android:color/white"
android:fitsSystemWindows="true">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="这是顶部按钮"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="这是中间的按钮"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="这是底部按钮"/>
</RelativeLayout>
</androidx.drawerlayout.widget.DrawerLayout>
- 监听DrawerLayout的侧滑状态,代码如下:
// 不要忘记AndoridManifest注册文件中修改启动活动为MainActivity2
public class MainActivity2 extends AppCompatActivity {
DrawerLayout drawer2;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_v2);
// 监听DrawerLayout的侧滑状态
drawer2 = (DrawerLayout) findViewById(R.id.drawer2);
//drawer2.setDrawerListener();//过时了
/*addDrawerListener()中的参数可以选择DrawerLayout,也可以选择DrawerLayout的子类SimpleDrawerListener
或者是ActionBarDrawerToggle*/
drawer2.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
}
@Override
public void onDrawerOpened(@NonNull View drawerView) {
Toast.makeText(MainActivity2.this,"大家快来看啊,侧滑菜单打开了!",Toast.LENGTH_SHORT).show();
}
@Override
public void onDrawerClosed(@NonNull View drawerView) {
Toast.makeText(MainActivity2.this,"大家快来看啊,侧滑菜单关闭了!",Toast.LENGTH_SHORT).show();
}
@Override
public void onDrawerStateChanged(int newState) {
}
});
}
}
- 效果如下