Java / 侧滑菜单:DrawerLayout

侧滑菜单:DrawerLayout

侧滑菜单是用来在页面上增加一个抽屉式菜单栏的控件,它一般位于左侧,用户可以通过侧滑拉出或者关闭。通常你可以放置一些菜单项或者上下文相关的设置在里面,帮助你节省屏幕空间同时可以很方便的随时打开。侧滑菜单其实就是下面这货:

DrawerLayout

1. 侧滑菜单的特性

侧滑菜单在 Andriod 应用中非常常见,但是当你想探究实现方法的时候会发现很多早期教程都会教你使用第三方库,或者手把手教你通过一个 ListView 配合手势加上动画来实现。好消息是现在官方已经推出了一个专门用于侧滑的控件——DrawerLayout。
DrawerLayout 作为页面内容的顶层容器,让用户通过侧滑手势从屏幕边缘拉出。我们可以给它的子 View 设置layout_gravity属性来决定抽屉是从左侧或者右侧打开。

DrawerLayoutGravity

2. DrawerLayout 使用方法

DrawerLayout 作为官方提供的控件,使用起来还是很简单的,如果查看 DrawerLayout 的源码,可以发现 DrawerLayout 是直接继承至 ViewGroup 写的,所以在它内部可以承载其他的 View 同时还会有一些附属的功能。我们通常将 DrawerLayout 用做根View,然后将首页要展示的内容放在 DrawerLayout 的最前面作为第一个子 View,并且高度、宽度设置为match_parent

2.1 DrawerLayout 的常用属性

DrawerLayout 的特有属性非常少,常用的基本上就是在前文提到过的一个:layout_gravity,它的作用是指定侧滑菜单的滑动方向,常用的有以下两个值:

  • start: 从左侧划出的菜单
  • end: 从右侧划出的菜单
    该属性是作用在 DrawerLayout 的子 View 之上的,设置了layout_gravity属性的子 View 将作为一个侧滑菜单使用。

2.2 DrawerLayout 的常用 API

常用 API 只有一个,用来监听策划菜单的滑动事件:

public void addDrawerListener(DrawerListener listener) 

接口中有以下几个方法:

  • public void onDrawerSlide(View drawerView, float slideOffset)
    当侧滑菜单滑动过程中调用,传入滑动的 View 对象及滑动的距离
  • public void onDrawerOpened(View drawerView)
    当侧滑菜单打开的时候调用
  • public void onDrawerClosed(@NonNull View drawerView)
    当侧滑菜单关闭的时候调用
  • public void onDrawerStateChanged(int newState)
    当侧滑菜单滑动状态变化时调用

整体而言,官方已经为我们封装的非常好了,所以使用也比较简单,只需要掌握这几个属性和 API,就可以轻松设置属于你自己的侧滑菜单了,接下来我们一起实现一个 DrawerLayout 示例。

3 DrawerLayout 的使用示例

本节我们通过 DrawerLayout 实现一个左侧的侧滑菜单,仍然借用“水果”作为案例(提醒大家注意健康)。在 DrawerLayout 中放置一个 ListView 作为侧滑菜单载体,用户通过在 ListView 上选择从而更新主页面的内容。

3.1 编写主页面布局

整体的页面布局对按照第 2 小节所讲的规则来编写,包含几个元素:主页面标题、主页面内容、侧滑菜单。布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.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">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="@string/drawer_prompt"
        android:textSize="30sp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center"
            android:text="侧滑菜单" />

        <ImageView
            android:id="@+id/imageview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>

    <ListView
        android:id="@+id/left_drawer"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="#FFFFFF"
        android:choiceMode="singleChoice"
        android:divider="@android:color/darker_gray"
        android:dividerHeight="1dp" />
</androidx.drawerlayout.widget.DrawerLayout>

3.2 编写列表布局

接着就是要针对 ListView 编写一个列表样式了,学过 ListView 的同学想必都不在话下,如果对 ListView 不熟悉的可以回顾一下第 24 节的内容,这里我们新建 list_item.xml 文件,用最经典的 TextView 搭配 ImageView 的方式:

<?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="100dp"
    android:gravity="center"
    android:orientation="horizontal"
    android:background="?android:attr/activatedBackgroundIndicator"
    android:minHeight="?android:attr/listPreferredItemHeightSmall"
    android:padding="10dp" >

    <ImageView
        android:id="@+id/imageViewIcon"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:paddingRight="10dp" />

    <TextView
        android:id="@+id/textViewName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="15dp"
        android:textColor="@android:color/black"
        android:textAppearance="?android:attr/textAppearanceListItemSmall" />

</LinearLayout>

3.3 编写 ListView 适配器

其实整个项目针对 DrawerLayout 的操作并不多,更多的是在实现 ListView,这也体现了 Google 大佬们经典的封装能力。有了 ListView 布局就需要添加一个 Adapter,以供将 UI 和 数据绑定到一起。

package com.emercy.myapplication;

import android.view.View;
import android.view.ViewGroup;

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class MyAdapter extends ArrayAdapter<Model> {

    Context mContext;
    int layoutResourceId;
    Model data[];

    public MyAdapter(Context mContext, int layoutResourceId, Model[] data) {

        super(mContext, layoutResourceId, data);
        this.layoutResourceId = layoutResourceId;
        this.mContext = mContext;
        this.data = data;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        View listItem = convertView;

        LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
        listItem = inflater.inflate(layoutResourceId, parent, false);

        ImageView imageViewIcon = (ImageView) listItem.findViewById(R.id.imageViewIcon);
        TextView textViewName = (TextView) listItem.findViewById(R.id.textViewName);

        Model folder = data[position];


        imageViewIcon.setImageResource(folder.icon);
        textViewName.setText(folder.name);

        return listItem;
    }
}

3.4 编写 MainActivity 主逻辑

最后就可以进入主逻辑的编写,我们需要初始化列表数据,然后通过 Adapter 与 ListView 绑定,然后监听列表的点击也就是用户的选择,再点击的时候主页面做出相应的改变即可。

package com.emercy.myapplication;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;

public class MainActivity extends Activity {

    private ListView mDrawerList;
    private ImageView mImage;

    Model[] mItem = new Model[3];


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDrawerList = (ListView) findViewById(R.id.left_drawer);
        mImage = findViewById(R.id.imageview);

        mItem[0] = new Model(R.drawable.apple, "苹果");
        mItem[1] = new Model(R.drawable.peach, "桃子");
        mItem[2] = new Model(R.drawable.watermelon, "西瓜");

        MyAdapter adapter = new MyAdapter(this, R.layout.list_item, mItem);
        mDrawerList.setAdapter(adapter);
        mDrawerList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                mImage.setImageResource(mItem[position].icon);
            }
        });
    }
}

最后我们编译运行,会发现整个页面只有一个“选择水果”的提示信息,不着急,侧滑菜单当然是默认隐藏的,我们通过在屏幕左侧侧滑来请它出山,最终效果如下:

DrawLayoutExample

4. 小结

本节介绍了官方出品的一个侧滑菜单控件,它是直接继承自 ViewGroup 实现的,通常我们会把它作为整个页面的根布局,然后将主页面作为 DrawerLayout 的第一个子 View。接着为需要侧滑的列表添加layout_gravity属性,最后按照需要添加一个监听器,就可以完成侧滑功能了。在 DrawerLayout 出来之前,有各式各样第三方的侧滑控件,大家可以翻看一些比较古老的帖子或者教程学习学习,但是使用上还是推荐使用官方 DrawerLayout 控件,在细节和封装以及后期维护上还是很具有优势的。