Android扩展知识 - DrawerLayout

借鉴以下博文

https://www.jianshu.com/p/d2b1689a23bf
https://blog.csdn.net/lmj623565791/article/details/46405409

简述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:headerLayoutapp: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) {

            }
        });

    }
}

  • 效果如下
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值