运行效果图:
前言
滑动菜单相信都不会陌生,你可能见过很多这样的文章,但我的文章会给你不一样的阅读和操作体验。
正文
写博客,自然是从创建项目开始了,这样你可以更好的知道这个过程中经历了什么。
一、创建项目
项目就命名为DrawerDemo,
绝对的手把手教学,让你清楚每一步怎么做。
然后打开app下的build.gradle,在android{}闭包中添加如下代码:
//配置JDK的版本 compileOptions { targetCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8 }
这里是配置JDK的版本,为1.8,我个人最喜欢的版本。一旦项目中的build.gradle有改动,便会出现Sync Now或者Sync的同步提示,如下图所示:
右上角的就是这个同步提示,如果你不点击进行同步,则你刚才在build.gradle中的改动无效,下面点击Sync Now。
二、添加滑动菜单
打开layout,找到activity_main.xml,修改代码后如下所示:
这里你可以选择复制粘贴到你的项目中,然后来说说这里的细节。
第一个细节就是页面根布局,DrawerLayout ,这个布局现在是在androidx下的,如果你还是v4或者v7的版本下的,请尽早迁移到androidx,至于怎么迁移?你更新Android Studio就可以了,目前最新的AS是4.1.3版本,你可以选择4.0.1就可以了,稳妥。
然后你注意到这个里面放了两个LinearLayout(线性布局),LinearLayout里面一个放了TextView,一个放了Button,居中显示,我这里特地在布局中增加了注释,告诉你哪一个是主页面,哪一个是滑动菜单。
那么是怎么分出来的呢?当你第一次看的时候你不知道为什么的时候,你就找不同,看看两个LinearLayout有什么不同,于是你会发现滑动菜单比主页面布局多了两个属性
android:layout_gravity="start" android:background="@color/colorAccent"
然后结果很明显了,background只能修改布局的布景,那么答案就是
android:layout_gravity="start"
这个属性是什么意思呢?layout_gravity值的是布局重力,这里你要和gravity区分出来,你记住有layout的代表控制本身,没有layout的代表控制内容。而它的属性值你看到是start,这种属性一般都是成对关系,有start自然就有end。而start是Android中新的用法,它代替了left,end代替了right。现在的left和right依然可以用,只不过Google更推荐你使用start和end表示左右。
那么你现在再来看这一行代码就知道是什么意思了。就是当前布局置于根布局的左侧,而因为你的根布局是DrawerLayout,因此你的预览界面应该只能看到页面主布局。
但是你要注意左边的这个蓝色线,这个代表了你当前的滑动菜单的位置。
此时你把start改成end,你再看预览界面就到了右边去了,表示从屏幕右侧出现。记得改回start。
布局介绍完毕了,下面我们通过点击主页面的按钮显示这个滑动菜单。
打开MainActivity,修改后代码如下:
public class MainActivity extends AppCompatActivity { private DrawerLayout drawerLayout;//滑动菜单 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); drawerLayout = findViewById(R.id.drawer_layout); findViewById(R.id.btn_open).setOnClickListener(v -> { //打开滑动菜单 左侧出现 drawerLayout.openDrawer(GravityCompat.START); }); } }
这里我创建一个变量,然后在onCreate中绑定布局id。然后在按钮的点击事件中,通过openDrawer方法打开滑动菜单,里面传入GravityCompat.START,START是左侧,GravityCompat是重力兼容,表示兼容低版本,在低版本的AS中你就要使用Gravity.START。
这里的START和布局中的start是要对应上的,如果你不对应上就会报错,那么下面运行一下吧。
嗯,这个效果是有了,但是感觉比较的丑,那么来美化一下吧。
三、UI美化
打开res下values文件夹中的styles.xml。
把DarkActionBar改成NoActionBar,意思是去掉默认的导航栏。
然后你再运行一下,你会发现好看了一点点。
不过屏幕顶部还是有那个很丑的状态栏,因此我们还需要美化一下。
在MainActivity中增加一个方法来设置状态栏透明。
/** * 透明状态栏 */ private void transparentStatusBar() { //改变状态栏颜色为透明 View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); getWindow().setStatusBarColor(Color.TRANSPARENT); }
这个方法先拿到当前Actvity的DecorView,它是Activity的根部视图,相当于最底层的视图,你想要详细的了解就需要去看源码了。
然后调用setSystemUiVisibility来改变系统UI的显示。View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN和View.SYSTEM_UI_FLAG_LAYOUT_STABLE表示Activity的布局会显示在状态栏上面,之后调用setStatusBarColor方法设置状态栏颜色为透明。
然后在onCreate中调用这个方法。
然后你还需要在activity_main.xml中去设置可以显示需要显示在状态栏中的布局。在主页面布局和滑动菜单的父布局中都添加一个属性:
android:fitsSystemWindows="true"
然后再给主页面设置一个背景图,背景图如下:
放到你项目的drawable文件夹下,然后在布局中设置
下面运行一下:
现在这个感觉怎么样呢?比之前是不是好多了呢?但是你会发现这个按钮有一些不上档次了,显得是辣么的突兀。我们像个办法去掉它。
首先修改布局,将之前的按钮替换为如下代码:
添加位置如下图
这里的icon_menu是一个图标,在你的drawable下新建一个icon_menu.xml文件,里面的代码如下:
这是一个白色的菜单图标,下面回到MainActivity中,先声明变量。
private DrawerLayout drawerLayout;//滑动菜单
然后在onCreate中添加如下代码:
toolbar = findViewById(R.id.toolbar); //工具栏按钮点击 toolbar.setNavigationOnClickListener(v -> drawerLayout.openDrawer(GravityCompat.START));
点击之后打开这个滑动菜单。
下面你再运行一下:
这样就简洁雅致了很多了。
四、添加导航视图
现在我们的滑动菜单用的是一个LinearLayout,虽然用起来没有很大的问题,但是如果有更好的控件为什么不用呢?下面就来介绍一下NavigationView,不过要在AS中使用这个控件还需要添加一个依赖库:
打开你app下的build.gradle,在dependencies{}闭包中添加如下依赖
//添加material库 implementation 'com.google.android.material:material:1.2.1'
添加之后记得点击Sync Now进行同步项目。
然后修改activity_main.xml,去掉之前的滑动菜单,修改的页面布局代码如下:
不过就算是这样,你当前还需要添加导航视图的头部视图和菜单视图。
在layout下新建一个nav_header.xml,作为导航的头部视图,常规的我们会在导航视图里面放置一些个人信息,头像,名称等。
那么头像是一个图片,而且普遍是圆形图片,常规是通过一些第三方库和自定义VIew来实现。还记得我们刚才导入的material库吗?可以用它里面的控件来实现圆形头像。
首先我们在styles.xml中增加如下代码:
然后在nav_header.xml增加一个图标控件和两个文字控件,里面代码如下:
这里有一个icon_default_avatar的图标,也是我博客的头像,如下图所示
那么这个导航视图的头部就写好了,下面来写导航菜单。
在这之前能先放置五个图标,都是通过路径来绘制的。都放在drawable下。
icon_friend.xml
icon_wallet.xml
icon_location.xml
icon_phone.xml
icon_email.xml
然后我们在res下新建一个menu文件夹,文件夹下新建一个nav_menu.xml文件。里面的代码如下:
此时你会在预览的地方看到这样的画面
不用担心,图标是有的,只不过和使用方式有关系。
下面我们回到这个activity_main.xml,把我们写的导头部和菜单都引入进NavigationView中。
运行一下吧。
这样的效果如何呢?当然我们还需要与用户交互才行,不然你就是中看不中用。
进入到MainActivity中,首先新建一个showMsg方法,用于弹出Toast提示。
/** * Toast提示 * @param msg 内容 */ private void showMsg(String msg){ Toast.makeText(this,msg,Toast.LENGTH_SHORT).show(); }
然后新增变量:
private NavigationView navView;//导航视图
然后在onCreate中绑定xml的id。
navView = findViewById(R.id.nav_view);
再通过这个navView来获取头部视图。
//获取头部视图 View headerView = navView.getHeaderView(0);
头部视图中常规的头像是有点击动作的了,那么可以这样写:
//头像点击 headerView.findViewById(R.id.iv_avatar).setOnClickListener(v -> showMsg("头像"));
然后就是菜单视图的点击了,如下所示,通过点击item的id进行判断,然后提示,之后关闭滑动菜单。
//导航菜单点击 navView.setNavigationItemSelectedListener(item -> { switch (item.getItemId()) { case R.id.item_friend: showMsg("朋友"); break; case R.id.item_wallet: showMsg("钱包"); break; case R.id.item_location: showMsg("位置"); break; case R.id.item_phone: showMsg("电话"); break; case R.id.item_email: showMsg("邮箱"); break; default: break; } //关闭滑动菜单 drawerLayout.closeDrawer(GravityCompat.START); return true; });
运行之后一一点击测试一下:
嗯,和预想的效果一致,这也是现在很多APP侧滑菜单的用法,基本上就差不多了。
五、菜单分类
假如上面的五个菜单是基础功能,那么下面再添加一个扩展菜单。
当然还是要先添加这个菜单图标
icon_share.xml
icon_send.xml
然后修改nav_menu.xml添加如下代码:
放在group的i下面,然后进入到MainActivity中,添加两个菜单的点击事件
运行
你可以看到这里还有分隔线。
六、动态菜单
像这种导航菜单一般都是定好的,静态的。但是保不齐就有需要动态的菜单,需要去动态改变一些数据。而动态的菜单就不能再去使用刚才的这种方式添加item了,我们可以用列表来解决。
说到列表你会想到ListView,不过现在都使用RecyclerView了。而为了简化RecyclerView的使用,我打算引入帮助的库,而为了模拟真实的接口返回数据,也会使用一个Json解析库。
下面首先在工程的下build.gradle的添加如下
//添加jitpack仓库 maven { url "https://jitpack.io" }
添加位置如下图:
为什么要这么做的呢?这里你就要分清楚Android依赖库的由来,Google自己的库和第三方库。Google自己的库在你创建项目时就已经添加了,如
google() jcenter()
而第三方库要想使用有些是需要添加jitpack仓库的,也就是我上面添加的代码。
下面进入app的build.gradle,在dependencies闭包{}中添加如下依赖库:
//RecyclerView最好的适配器,让你的适配器一目了然,告别代码冗余 implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30' //Gson库 implementation 'com.google.code.gson:gson:2.8.5'
然后再点击Sync Now 进行同步,同步没有报错,则我们先来构建这个返回的数据。
{ "code":200, "data":[ { "id":0, "name":"朋友" }, { "id":1, "name":"钱包" }, { "id":2, "name":"位置" }, { "id":3, "name":"电话" }, { "id":4, "name":"邮箱" }, { "id":5, "name":"分享" }, { "id":6, "name":"发送" } ], "msg":"Success" }
实际开发中的返回数据和这个差不多,仅供参考。这一段JSON字符串,里面有三个主体内容,code用来检测你返回的是否合格,通常200表示正常返回,msg则表示描述,对于这个code的描述。data则是返回的数据组数。下面通过这个返回数据,我们可以写出这样的一个实体类。
在com.llw.drawerdemo中新建一个MenuResponse.java类,里面的代码如下:
package com.llw.drawerdemo; import java.util.List; /** * 菜单返回数据 * @author lonel */ public class MenuResponse { /** * code : 200 * data : [{"id":0,"name":"朋友"},{"id":1,"name":"钱包"},{"id":2,"name":"位置"},{"id":3,"name":"电话"},{"id":4,"name":"邮箱"},{"id":5,"name":"分享"},{"id":6,"name":"发送"}] * msg : Success */ private int code; private String msg; private Listdata; public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public List getData() { return data; } public void setData(List data) { this.data = data; } public static class DataBean { /** * id : 0 * name : 朋友 */ private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } }
然后再创建一个常量类Contanst.java,里面的代码如下:
package com.llw.drawerdemo; /** * 常量 * @author lonel */ public class Contanst { public static final int SUCCESS = 200; public static final String JSON = "{\n" + " \"code\":200,\n" + " \"data\":[\n" + " {\n" + " \"id\":0,\n" + " \"name\":\"朋友\"\n" + " },\n" + " {\n" + " \"id\":1,\n" + " \"name\":\"钱包\"\n" + " },\n" + " {\n" + " \"id\":2,\n" + " \"name\":\"位置\"\n" + " },\n" + " {\n" + " \"id\":3,\n" + " \"name\":\"电话\"\n" + " },\n" + " {\n" + " \"id\":4,\n" + " \"name\":\"邮箱\"\n" + " },\n" + " {\n" + " \"id\":5,\n" + " \"name\":\"分享\"\n" + " },\n" + " {\n" + " \"id\":6,\n" + " \"name\":\"发送\"\n" + " }\n" + " \n" + " ],\n" + " \"msg\":\"Success\"\n" + "}"; }
里面是我们返回的数据JSON字符串,用这种方式来模拟真实返回数据。还有一点就是这个成功码用一个全局的常量来表示,尽量不要再代码中直接使用200来做为成功的返回判定。用常量的好处就是改起来快,假如今天你是200为成功,明天变成100,那么我只需要改这个常量的值即可,而不需要你去每一个用200判定的地方都去手动改成100,效率上就提高很多。
下面来写这个item的布局,在layout下新建一个item_menu.xml文件,里面的代码如下:
这里的
android:foreground="?attr/selectableItemBackground"
就是点击item的效果,体验感更强一些。
然后去写适配器,在com.llw.drawerdemo下新建一个MenuAdapter类,里面的代码如下:
package com.llw.drawerdemo; import androidx.annotation.Nullable; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; import java.util.List; /** * 菜单列表适配器 * @author lonel */ public class MenuAdapter extends BaseQuickAdapter{ public MenuAdapter(int layoutResId, @Nullable List data) { super(layoutResId, data); } @Override protected void convert(BaseViewHolder helper, MenuResponse.DataBean item) { helper.setText(R.id.tv_menu_name,item.getName()); //添加点击事件 helper.addOnClickListener(R.id.tv_menu_name); } }
相比于传统的适配器,这样写就简洁很多了,然后是修改布局,首先是activity_main.xml中,我们去掉nav_menu
保留这个headerLayout,然后去修改这个nav_header布局代码:修改后如下:
NestedScrollView表示一个滚动视图,它里面只能放一个布局,当这个布局的高度超过屏幕时,则可以上下滚动显示,而这个布局里面又可以嵌套其他的布局。我在里面放置了之前的相对布局和新增的列表控件。下面我们进入到MainActivity。
先声明成员变量
private RecyclerView rvMenu;//列表
然后在onCreate中通过headerView去绑定布局中的id。
//绑定列表控件 rvMenu = headerView.findViewById(R.id.rv_menu);
然后再写一个方法用来显示菜单列表。
/** * 显示菜单列表 */ private void showMenuList() { //解析JSON数据 MenuResponse menuResponse = new Gson().fromJson(Contanst.JSON, MenuResponse.class); if (menuResponse.getCode() == Contanst.SUCCESS) { //为空处理 if(menuResponse.getData() ==null){ showMsg("返回菜单数据为空"); return; } Listdata = menuResponse.getData(); //设置适配器的布局和数据源 MenuAdapter menuAdapter = new MenuAdapter(R.layout.item_menu, data); //item点击事件 menuAdapter.setOnItemChildClickListener((adapter, view, position) -> { showMsg(data.get(position).getName()); //关闭滑动菜单 drawerLayout.closeDrawer(GravityCompat.START); }); //设置线性布局管理器,默认是纵向 rvMenu.setLayoutManager(new LinearLayoutManager(this)); //设置适配器 rvMenu.setAdapter(menuAdapter); } else { //错误提示 showMsg(menuResponse.getMsg()); } }
里面的代码都有注释,相信你一定可以看懂。
然后在onCreate中调用这个方法
运行。
效果是有了,但是好像没有图标有点不得劲是吧。因为实际开发中的图标也是从后台返回过来的,一般来说是一个网络图标地址,这个地址你可以通过Glide库去进行图标显示。而我们没有这个网络地址,不过幸运的是,我们有之前手写的七个图标,不是吗。我们将这七个图标组成一个int数组,然后在适配器中进行配置就好了,不过首先呢需要改变一下item_menu.xml。
然后进入MenuAdapter中,
新增一个图标数组
private int[] iconArray = {R.drawable.icon_friend, R.drawable.icon_wallet, R.drawable.icon_location , R.drawable.icon_phone, R.drawable.icon_email, R.drawable.icon_share, R.drawable.icon_send};
然后在convert中通过item的位置来获取图标数组中的图标,然后设置到ImageVIew中,这样写是有弊端的,当你的数据条目和图标数组长度不一致时,就会出现数组越界,然后就报错崩溃,程序闪退,因此实际中不会采取这种方式,我这里只是演示。其次我还改变了添加点击事件的图标,之前是给TextView添加点击事件,现在是给LinearLayout添加点击事件。
然后我们回到MainActivity中,去给item添加分割线。
//添加item的分隔线 rvMenu.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
添加位置如下:
下面再运行一次。
这里菜单图标有了,分割线也有了,不过实际为了美感,通常会去掉最后一个item的分隔线,这个可以通过自定义View来实现,网上多的是。继承RecyclerView.ItemDecoration然后获取item数量,最后一个item不绘制分割线。
我这里就不详细介绍这种方式了,我们可以用另一种巧妙的方式来解决:
添加静态菜单。
下面再添加两个图标,
item_setting.xml
icon_logout.xml
然后修改之前的nav_menu.xml,修改后如下:
然后在styles.xml中增加一个item字体的大小设置
再进入activity_main.xml,修改NavigationView,修改内容如下:
再进入MainActivity中,修改菜单点击事件
然后运行一下:
嗯,这样你现在动态也有静态也有,挺好的,本文就写到这里了。
上高水长,后会有期。
七、源码
源码地址:DrawerDemo
到此这篇关于Android 侧滑抽屉菜单的实现代码的文章就介绍到这了,更多相关Android 侧滑抽屉菜单内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!