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必须设置成startleft,否则抽屉不会从左边滑出来。我见过有人写成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都可以设置icontitle,甚至checkable属性。如果你想让某个菜单项默认选中,可以加android:checked="true"

小技巧: 我建议菜单项不要超过7个。超过的话,用户滑动起来会有点累。如果实在多,可以考虑分组(<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的核心知识点:

NavigationView DrawerLayout 配合 menu 资源文件 headerLayout item 定义 icon / title checkable 属性 自定义布局 头像 / 昵称 setCheckedItem 点击监听 关闭抽屉 核心:布局 + 菜单 + Header + 选中状态

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