40、Material Design之NavigationView:侧滑菜单、菜单项、Header布局、选中状态
侧滑菜单,几乎是每个App的标配了。你想想看,用户一划,设置、个人中心、退出登录全出来了,多方便。在Material Design体系里,NavigationView就是干这个活的。
我个人习惯把NavigationView叫做「导航抽屉」——它和DrawerLayout配合使用,能快速搭建出一个带侧滑菜单的界面。今天我们就把它拆开揉碎了讲清楚。
1. NavigationView是什么?
说白了,NavigationView就是一个封装好的侧滑面板。它内部自带了一个RecyclerView来展示菜单项,还支持一个可自定义的Header布局。
它的核心结构是这样的:
<androidx.drawerlayout.widget.DrawerLayout>
<!-- 主内容区域 -->
<FrameLayout ... />
<com.google.android.material.navigation.NavigationView
android:layout_gravity="start"
app:menu="@menu/nav_menu"
app:headerLayout="@layout/nav_header" />
</androidx.drawerlayout.widget.DrawerLayout>
嗯,这里要注意:layout_gravity必须设置成start或left,否则抽屉不会从左边滑出来。我见过有人写成top,结果菜单从上面掉下来了……
2. 菜单项的定义
菜单项是通过menu资源文件来定义的。你可以在res/menu/目录下新建一个XML文件,比如nav_menu.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/nav_home"
android:icon="@drawable/ic_home"
android:title="首页" />
<item
android:id="@+id/nav_settings"
android:icon="@drawable/ic_settings"
android:title="设置" />
<item
android:id="@+id/nav_about"
android:icon="@drawable/ic_info"
android:title="关于我们" />
</menu>
每个item都可以设置icon、title,甚至checkable属性。如果你想让某个菜单项默认选中,可以加android:checked="true"。
<group>)或者用二级菜单。
3. Header布局
Header布局就是侧滑菜单顶部的那个区域,通常用来放用户头像、昵称、背景图等。你可以在res/layout/下新建一个布局文件,比如nav_header.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"
android:orientation="vertical"
android:gravity="center"
android:background="@color/primary">
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/ic_avatar"
android:background="@drawable/circle_shape" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户昵称"
android:textColor="@android:color/white"
android:textSize="16sp" />
</LinearLayout>
然后在NavigationView里通过app:headerLayout="@layout/nav_header"引用即可。
我曾经在项目里遇到一个问题:Header布局的高度写死了,结果不同屏幕尺寸下显示效果很差。后来我改用android:minHeight配合adjustViewBounds,才解决了适配问题。
4. 选中状态的管理
NavigationView默认支持选中状态。你只需要在菜单项的item里加上android:checkable="true",然后通过代码设置选中项:
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setCheckedItem(R.id.nav_home);
当用户点击某个菜单项时,它会自动变成选中状态,其他项会取消选中。这个行为是框架自带的,不需要你手动维护。
onNavigationItemSelected里手动调用了setCheckedItem,可能会导致重复选中或状态错乱。我建议只在初始化时调用一次,后续交给系统处理。
5. 监听菜单点击事件
要处理菜单项的点击,你需要实现OnNavigationItemSelectedListener:
navigationView.setNavigationItemSelectedListener(item -> {
switch (item.getItemId()) {
case R.id.nav_home:
// 切换到首页
break;
case R.id.nav_settings:
// 跳转到设置页
break;
case R.id.nav_about:
// 显示关于弹窗
break;
}
// 关闭侧滑菜单
drawerLayout.closeDrawer(GravityCompat.START);
return true;
});
这里有个细节:return true表示事件已消费,系统会帮你处理选中状态。如果返回false,选中状态就不会更新。
6. 知识体系结构图
下面这张图帮你理清NavigationView的核心知识点:
7. 完整示例代码
最后,给你一个完整的Activity示例,把上面所有知识点串起来:
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawerLayout;
private NavigationView navigationView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawerLayout = findViewById(R.id.drawer_layout);
navigationView = findViewById(R.id.nav_view);
// 默认选中首页
navigationView.setCheckedItem(R.id.nav_home);
// 设置点击监听
navigationView.setNavigationItemSelectedListener(item -> {
int id = item.getItemId();
if (id == R.id.nav_home) {
// 处理首页点击
} else if (id == R.id.nav_settings) {
// 处理设置点击
} else if (id == R.id.nav_about) {
// 处理关于点击
}
drawerLayout.closeDrawer(GravityCompat.START);
return true;
});
}
}
- NavigationView必须放在DrawerLayout内,且设置
layout_gravity="start" - 菜单项通过
menu资源定义,支持icon、title、checkable - Header布局通过
headerLayout引用,可自定义任意视图 - 选中状态通过
setCheckedItem设置,点击事件返回true自动管理
好了,NavigationView的核心用法就这些。你在项目里用的时候,记得注意适配和状态管理。我曾经因为忘记关闭抽屉,导致用户点完菜单还得手动划回去,体验很差。嗯,细节决定成败。
公众号:蓝海资料掘金营,微信deep3321