文章目录
一、什么是 BNV
BottomNavigationView(简称 BNV) 是 Material Components 库提供的一个底部导航控件,固定在页面底部,用于在 3~5 个顶层页面之间快速切换,常见于「首页 / 视频 / 我的」这类 App 结构。
它自带图标 + 文字、选中态变色、点击涟漪反馈,开箱即用,是 Android 官方推荐的底部导航方案。
二、为什么用它
- 符合 Material 规范:官方组件,样式统一,不用自己手写底部栏。
- 省事:菜单、选中态、点击反馈都由组件内部处理,只需提供菜单资源。
- 适合顶层导航:底部是拇指最容易触达的区域,适合放最常用的几个一级入口。
对比一下:TabLayout 一般放在顶部,适合配合 ViewPager2 做内容分页;BNV 放在底部,适合做 App 的全局一级导航。两者用途不同,有时也会一起使用。
三、引入依赖
Material 组件库(新建项目一般已默认带上):
groovy
implementation 'com.google.android.material:material:1.12.0'
四、定义菜单
BNV 的每个 Tab 对应一个菜单项,先建 res/menu/bottom_main_menu.xml:
xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/first_page" android:title="首页" android:icon="@drawable/ic_home" />
<item android:id="@+id/screen_page" android:title="视频" android:icon="@drawable/ic_screen" />
<item android:id="@+id/song_page" android:title="刷歌" android:icon="@drawable/ic_song" />
<item android:id="@+id/star_page" android:title="星光" android:icon="@drawable/ic_star" />
<item android:id="@+id/my_page" android:title="我的" android:icon="@drawable/ic_user" />
</menu>
菜单项建议控制在 3~5 个:少于 3 个没必要用底部栏,多于 5 个 Material 会退化成「更多」模式,体验变差。
五、布局中声明
xml
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF"
app:itemIconSize="20dp"
app:itemIconTint="@color/item_color"
app:itemTextColor="@color/item_color"
app:itemRippleColor="@android:color/transparent"
app:itemActiveIndicatorStyle="@null"
app:labelVisibilityMode="labeled"
app:menu="@menu/bottom_main_menu"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
六、常用属性
| 属性 | 作用 |
|---|---|
app:menu |
绑定菜单资源,决定有几个 Tab |
app:itemIconSize |
统一图标尺寸,避免菜单里图标大小不一 |
app:itemIconTint |
图标着色,传 ColorStateList 实现选中变色 |
app:itemTextColor |
文字着色,同样用 ColorStateList |
app:itemActiveIndicatorStyle |
Material3 的选中胶囊背景,传 @null 可去掉 |
app:itemRippleColor |
点击涟漪颜色,传透明可禁用 |
app:labelVisibilityMode |
labeled / unlabeled / selected / auto |
选中 / 未选中变色,res/color/item_color.xml:
xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#000000" android:state_selected="true"/>
<item android:color="#AAAAAA" android:state_selected="false"/>
</selector>
七、点击事件
java
bnv.setOnItemSelectedListener(item -> {
int id = item.getItemId();
if (id == R.id.first_page) {
switchTo(R.id.first_page);
return true; // 返回 true 表示已消费该事件,Tab 会保持选中
}
return false;
});
八、配合 Fragment 切换页面
点击 Tab 后一般用 FragmentManager 切换页面。推荐缓存实例,避免每次点击都重新创建 Fragment:
java
private final HashMap<Integer, Fragment> pages = new HashMap<>();
private Fragment currentFragment;
private void switchTo(int tabId) {
Fragment target = pages.get(tabId);
if (target == null) {
if (tabId == R.id.first_page) {
target = new HomeFragment();
} else {
return; // 还没做的页面先忽略
}
pages.put(tabId, target);
}
if (target == currentFragment) return;
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, target)
.commit();
currentFragment = target;
}
用 HashMap 缓存后,切回某个 Tab 时页面里的滚动位置、列表状态都能保留,不会每次被重置。
九、和 ViewPager2 联动
如果每个 Tab 的内容需要横向滑动切换,可以让 BNV 和 ViewPager2 双向联动:
java
// ViewPager2 滑动时同步选中 BNV 的 Tab
viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
bnv.getMenu().getItem(position).setChecked(true);
}
});
// 点击 BNV 时让 ViewPager2 滚过去
bnv.setOnItemSelectedListener(item -> {
int index = /* 菜单项对应的下标 */;
viewPager.setCurrentItem(index, false);
return true;
});
这种方式适合页面较少、且切换有明显左右滑动语义的场景。
十、常见问题
-
Material3 多了一个灰色胶囊背景 加
app:itemActiveIndicatorStyle="@null"去掉。 -
各 Tab 图标大小不一致 菜单里的矢量图 viewport 可能不同,用
app:itemIconSize统一强制尺寸。 -
点击后 Tab 又跳回原来那个
setOnItemSelectedListener里记得对已处理的项return true。
十一、小结
- 菜单控制在 3~5 项,保持顶层结构清晰;
- 用 ColorStateList 统一选中 / 未选中配色;
- 切换页面建议缓存 Fragment 实例,避免状态丢失;
- 需要横向滑动时再考虑和
ViewPager2联动; - 注意沉浸式布局下给底部留出系统导航栏高度。
BNV 本身开箱即用,把这些细节做好,底部导航就从「能用」变成「好用」了。