Android NavigationDrawer(侧滑导航栏 DrawerLayout + NavigationView)
本文由 Luzhuo 编写,转发请保留该信息. 原文: https://blog.csdn.net/Rozol/article/details/90487113
Google官方出品的侧滑导航栏, 可替换长期未维护的SlidingMenu. 除此之外, 还有个第三方开源库MaterialDrawer可供选择
Navigation Drawer 是 Material Design 中的侧滑导航栏, 由抽屉布局 DrawerLayout 和 导航View NavigationView 组成.
快速创建
新建项目时:
新建项目时, 最后一页"Add an Activity to Mobile" 选择 Navigation Drawer Activity 新建Activity时:
在已有的项目, 创建Activity选择 Navigation Drawer Activity
手撸实现
抽屉布局 DrawerLayout
<?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_base"
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_base"
app:menu="@menu/activity_base_drawer" />
</android.support.v4.widget.DrawerLayout>
其中 NavigationView 的 android:layout_gravity="start"属性控制左右滑方向.
DrawerLayout 与 Toolbar 的结合
private DrawerLayout drawer
;
@Override
protected void onCreate(Bundle savedInstanceState
) {
super.onCreate(savedInstanceState
);
setContentView(R
.layout
.activity_base
);
initDrawerLayout();
}
private void initDrawerLayout() {
Toolbar toolbar
= findViewById(R
.id
.toolbar
);
setSupportActionBar(toolbar
);
drawer
= findViewById(R
.id
.drawer_layout
);
ActionBarDrawerToggle toggle
= new ActionBarDrawerToggle(this, drawer
, toolbar
, R
.string
.navigation_drawer_open
, R
.string
.navigation_drawer_close
);
drawer
.addDrawerListener(toggle
);
toggle
.syncState();
}
返回键的处理
@Override
public void onBackPressed() {
if (drawer == null) return;
// 返回键: 侧滑开着就将其关闭, 关着则退出应用
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
打开 / 关闭 / 锁定
// 打开
drawer.openDrawer(GravityCompat.START);
drawer.openDrawer(GravityCompat.END);
drawer.openDrawer(GravityCompat.START, true); // 默认true, 执行动画
drawer.openDrawer(navigationView); // 打开指定的 navigationView
// 关闭
drawer.closeDrawer(GravityCompat.START);
drawer.closeDrawers(); // 关闭所有
// 打开并锁定
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, GravityCompat.START);
参数Description
GravityCompat.START左侧打开GravityCompat.END右侧打开——DrawerLayout.LOCK_MODE_LOCKED_CLOSED关闭锁 (锁着, 不让手势打开)DrawerLayout.LOCK_MODE_LOCKED_OPEN开启锁 (开着, 不让手势关闭)DrawerLayout.LOCK_MODE_UNDEFINED默认锁 (默认是不上锁)DrawerLayout.LOCK_MODE_UNLOCKED不锁 (不上锁)
注意: 使用方向(如: GravityCompat.START) 参数时, 子布局必须有指定layout_gravity属性(如: NavigationView 的 android:layout_gravity="start")与之相匹配, 否则会找不到.
阴影的颜色
// (被遮挡部分的)阴影部分的颜色
drawer.setScrimColor(Color.parseColor("#66666666"));
监听滑动事件
drawer.addDrawerListener(drawerListener);
private DrawerLayout.DrawerListener drawerListener = new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(@NonNull View view, float v) {
Log.i(TAG, "onDrawerSlide: 滑动时:" + v);
}
@Override
public void onDrawerOpened(@NonNull View view) {
Log.i(TAG, "onDrawerOpened: 打开后");
}
@Override
public void onDrawerClosed(@NonNull View view) {
Log.i(TAG, "onDrawerClosed: 关闭后");
}
@Override
public void onDrawerStateChanged(int i) {
// 滑动状态
switch (i){
case DrawerLayout.STATE_DRAGGING:
Log.i(TAG, "onDrawerStateChanged: 滑动状态");
break;
case DrawerLayout.STATE_IDLE:
Log.i(TAG, "onDrawerStateChanged: 静止状态");
break;
case DrawerLayout.STATE_SETTLING:
// 设置状态在静止状态之前调用, 表示正在调整到最终位置
Log.i(TAG, "onDrawerStateChanged: 设置状态");
break;
default:
break;
}
}
};
导航View NavigationView
标准的导航菜单, NavigationView 通常放在 DrawerLayout 中使用.
上部分是Header, 下部分是Menu, 当然这些都是可选的. Menu具有不错的默认样式: 选中效果 / 分组 / 分组子标题 / Header 等.
DrawerLayout是v4包下的, 而 NavigationView 则是design包下的.
implementation 'com.android.support:design:28.0.0'
添加Header和Menu布局
通过app:headerLayout属性添加Header; 通过app:menu属性添加Menu.
<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_base"
app:menu="@menu/activity_base_drawer" />
编写Menu布局
Header就是普通的布局没什么好讲的, 这里讲下Menu的编写.
文件放在 res -> menu 文件夹下.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
// 有选中效果的菜单组
<group android:checkableBehavior="single">
<item // 菜单条目
android:id="@+id/nav_camera"
android:icon="@drawable/ic_menu_camera" // 图标
android:title="Import" /> // 名称
<item
android:id="@+id/nav_gallery"
android:icon="@drawable/ic_menu_gallery"
android:title="Gallery" />
<item
android:id="@+id/nav_slideshow"
android:icon="@drawable/ic_menu_slideshow"
android:title="Slideshow" />
<item
android:id="@+id/nav_manage"
android:icon="@drawable/ic_menu_manage"
android:title="Tools" />
</group>
// 没有选中效果的普通菜单条目, title子标题
<item android:title="Communicate">
<menu> // 子菜单
<item
android:id="@+id/nav_share"
android:icon="@drawable/ic_menu_share" // 图标
android:title="Share" /> // 标题
<item
android:id="@+id/nav_send"
android:icon="@drawable/ic_menu_send"
android:title="Send" />
</menu>
</item>
</menu>
设置Menu的选择监听
但用户选择完之后需要手动将侧滑导航栏关闭.
private void intiDrawerView() {
navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.nav_camera:
break;
case R.id.nav_gallery:
break;
case R.id.nav_slideshow:
break;
case R.id.nav_manage:
break;
case R.id.nav_share:
break;
case R.id.nav_send:
break;
default:
break;
}
// 关闭侧滑导航栏
drawer.closeDrawer(GravityCompat.START);
return true;
}
默认的情况下, 用户不点, Menu是没有选中的Item的, 可以通过setCheckedItem(int id)方法设置选中. 且 onNavigationItemSelected是不会回调的.
// 设置第一个选中
navigationView.setCheckedItem(R.id.nav_camera);
关于Menu图标的颜色
我选了张彩色的图标:
默认显示的图标:
去除图标颜色显示规则, 显示为原色:
// 去除图标颜色显示规则, 显示为原色
navigationView.setItemIconTintList(null);
指定图标颜色显示规则(显示不同颜色): 在drawable下创建nav_menu_icon_color选择器资源文件.
// 有-为未选中的颜色, 没有-为选中的颜色
int[][] states = new int[][]{new int[]{-android.R.attr.state_checked},new int[]{android.R.attr.state_checked} };
int[] colors = new int[]{getResources().getColor(R.color.colorPrimaryDark), getResources().getColor(R.color.colorAccent) };
ColorStateList colorStateList = new ColorStateList(states, colors);
navigationView.setItemIconTintList(colorStateList);
指定图标的显示规则(显示不同图标): // 未找到相关方法, 目前不支持
虽然系统未提供相关方法, 都是你可以通过修改图标自行实现, 就是麻烦些.
// 修改图标
navigationView.getMenu().findItem(R.id.nav_camera).setIcon(R.mipmap.menu_caomei);
关于Menu文字的颜色
设置的设置方法与图标是一样的, 这样不展开讲了.
navigationView.setItemTextColor(colorStateList);
2. 在真实场景中的应用 NavigationDrawer + Navigation
有读者评论说 NavigationDrawer 没有提供切换Fragment的方法, 它没有提供是因为这本来就不是它的职责范围, 它已经提供了菜单按钮的点击回调了 该案例使用 Navigation 来切换 Fragment, 当然你也可以用其他的, 比如 FrameLayout
1. 创建 Navigation
1. 创建Fragment (略)
2. 创建 Navigation 路由文件
在 res 目录下创建 navigation 文件夹在 navigation 文件夹中创建 use_navigation.xml 文件 (名字随意)编写配置文件
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/mobile_navigation"
app:startDestination="@id/nav_first">
<fragment
android:id="@+id/nav_first"
android:name="me.luzhuo.navigationdrawerdemo.use.fragment.FirstFragment"
android:label="First" />
<fragment
android:id="@+id/nav_second"
android:name="me.luzhuo.navigationdrawerdemo.use.fragment.SecondFragment"
android:label="Second" />
<fragment
android:id="@+id/nav_three"
android:name="me.luzhuo.navigationdrawerdemo.use.fragment.ThreeFragment"
android:label="Three" />
</navigation>
标记第一个Fragment为起始Fragment
3. 配置 NavHostFragment
创建 app_bar_use.xml 文件 (名字随意)
这里是主页显示的内容, 根据不同的需求进行修改如果不想使用 Navigation 实现切换 Framgent 的方式, 那就把这里的内容换掉比如想用 FrameLayout 实现切换 Fragment, 那就把这里的 fragment 换成 FrameLayout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/use_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
2. 创建 NavigationDrawer
1. 创建布局文件 activity_use.xml (名字随意)
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.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_use"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.navigation.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_use"
app:menu="@menu/activity_use_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
2. 创建菜单头布局文件 nav_header_use.xml
<?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:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height"
android:background="@drawable/side_nav_bar"
android:gravity="bottom"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/nav_header_desc"
android:paddingTop="@dimen/nav_header_vertical_spacing"
app:srcCompat="@mipmap/ic_launcher_round" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:text="@string/nav_header_title"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nav_header_subtitle" />
</LinearLayout>
3. 创建配置文件 activity_use_drawer.xml
注意: item 的 id 要和 Navigation 配置的路由 id 一致
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_first"
android:icon="@mipmap/menu_bangbing"
android:title="First" />
<item
android:id="@+id/nav_second"
android:icon="@drawable/ic_menu_gallery"
android:title="Second" />
<item
android:id="@+id/nav_three"
android:icon="@drawable/ic_menu_slideshow"
android:title="Three" />
</group>
</menu>
3. 将 Navigation 和 NavigationDrawer 组合起来使用
public class UseActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private DrawerLayout drawer
;
private NavigationView navigationView
;
private NavController navController
;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState
) {
super.onCreate(savedInstanceState
);
setContentView(R
.layout
.activity_use
);
drawer
= findViewById(R
.id
.drawer_layout
);
navigationView
= findViewById(R
.id
.nav_view
);
ActionBarDrawerToggle toggle
= new ActionBarDrawerToggle(this, drawer
, null
, R
.string
.navigation_drawer_open
, R
.string
.navigation_drawer_close
);
drawer
.addDrawerListener(toggle
);
toggle
.syncState();
navController
= Navigation
.findNavController(this, R
.id
.nav_host_fragment
);
navigationView
.setCheckedItem(R
.id
.nav_first
);
navigationView
.setNavigationItemSelectedListener(this);
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item
) {
navController
.navigate(item
.getItemId());
navigationView
.setCheckedItem(item
);
drawer
.closeDrawer(GravityCompat
.START
);
return true;
}
@Override
public void onBackPressed() {
if (drawer
== null
&& drawer
.isDrawerOpen(GravityCompat
.START
)) drawer
.closeDrawer(GravityCompat
.START
);
else super.onBackPressed();
}
}
<string name="navigation_drawer_open">Open navigation drawer
</string>
<string name="navigation_drawer_close">Close navigation drawer
</string>