BottomNavigationView 是 Android Support Library(现为 AndroidX)提供的底部导航组件,用于实现应用底部的tab切换功能,适用于3-5个导航项的场景,符合 Material Design 设计规范,能提升用户操作便捷性。
一、核心特性
- 支持图标+文字/纯图标两种显示模式
- 自带选中态切换动画(颜色、大小变化)
- 支持徽章(Badge)显示未读消息数
- 可配置导航项点击事件、选中状态监听
- 适配 AndroidX,支持深色模式
- 支持自定义图标、文字样式、背景色
二、基础使用步骤
1. 依赖配置(AndroidX)
确保项目已引入 Material Components 依赖(AndroidX 必备):
dependencies {
// Material Components(包含 BottomNavigationView)
implementation "com.google.android.material:material:1.12.0"
// 其他基础依赖(如 AppCompat)
implementation "androidx.appcompat:appcompat:1.6.1"
}
2. 布局文件(XML)
在 Activity/Fragment 布局中添加 BottomNavigationView,通常与 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">
<!-- 内容区域(用于切换 Fragment) -->
<FrameLayout
android:id="@+id/fl_content"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/bottom_nav"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<!-- 底部导航栏 -->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:menu="@menu/menu_bottom_nav"
app:itemIconTint="@color/bottom_nav_selected"
app:itemIconTintMode="src_in"
app:itemTextColor="@color/bottom_nav_selected"
app:itemTextAppearanceActive="@style/BottomNavTextActive"
app:itemTextAppearanceInactive="@style/BottomNavTextInactive"
app:itemPaddingHorizontal="16dp"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
3. 导航菜单资源(menu.xml)
在 res/menu 目录下创建导航项配置文件 menu_bottom_nav.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 首页 -->
<item
android:id="@+id/nav_home"
android:icon="@drawable/ic_home"
android:title="首页"
app:iconTint="@color/bottom_nav_unselected"
app:selectedIcon="@drawable/ic_home_selected"/>
<!-- 发现 -->
<item
android:id="@+id/nav_discover"
android:icon="@drawable/ic_discover"
android:title="发现"
app:selectedIcon="@drawable/ic_discover_selected"/>
<!-- 我的 -->
<item
android:id="@+id/nav_mine"
android:icon="@drawable/ic_mine"
android:title="我的"
app:selectedIcon="@drawable/ic_mine_selected"/>
</menu>
4. 颜色/样式资源配置
在 res/values/colors.xml 中定义导航栏颜色:
<resources>
<!-- 选中颜色 -->
<color name="bottom_nav_selected">#FF6200EE</color>
<!-- 未选中颜色 -->
<color name="bottom_nav_unselected">#8A000000</color>
</resources>
在 res/values/styles.xml 中定义文字样式(可选):
<style name="BottomNavTextActive" parent="TextAppearance.AppCompat.Caption">
<item name="android:textSize">12sp</item>
<item name="android:textStyle">bold</item>
</style>
<style name="BottomNavTextInactive" parent="TextAppearance.AppCompat.Caption">
<item name="android:textSize">11sp</item>
</style>
5. Kotlin 代码实现(核心逻辑)
5.1 基础功能:Fragment 切换
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.MenuItem
import androidx.fragment.app.Fragment
import com.google.android.material.bottomnavigation.BottomNavigationView
class MainActivity : AppCompatActivity() {
// 初始化三个 Fragment
private val homeFragment = HomeFragment()
private val discoverFragment = DiscoverFragment()
private val mineFragment = MineFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val bottomNav: BottomNavigationView = findViewById(R.id.bottom_nav)
// 初始显示首页 Fragment
supportFragmentManager.beginTransaction()
.replace(R.id.fl_content, homeFragment)
.commit()
// 导航项点击事件监听
bottomNav.setOnItemSelectedListener { menuItem: MenuItem ->
// 根据菜单 ID 切换对应的 Fragment
val targetFragment: Fragment = when (menuItem.itemId) {
R.id.nav_home -> homeFragment
R.id.nav_discover -> discoverFragment
R.id.nav_mine -> mineFragment
else -> homeFragment
}
// 替换内容区域的 Fragment
supportFragmentManager.beginTransaction()
.replace(R.id.fl_content, targetFragment)
.commit()
// 返回 true 表示消费该事件(选中状态生效)
true
}
// 可选:设置默认选中项(默认选中第一个)
bottomNav.selectedItemId = R.id.nav_home
}
}
5.2 进阶功能:添加徽章(Badge)
通过 BadgeDrawable 实现未读消息数显示:
// 在 onCreate 中添加徽章逻辑
val bottomNav: BottomNavigationView = findViewById(R.id.bottom_nav)
// 给「发现」导航项添加徽章
val discoverMenuId = R.id.nav_discover
val badge = bottomNav.getOrCreateBadge(discoverMenuId)
badge.isVisible = true // 显示徽章
badge.number = 99 // 徽章数字(超过99显示99+)
badge.badgeBackgroundColor = resources.getColor(R.color.red, theme) // 徽章背景色
badge.maxCharacterCount = 2 // 最大显示字符数(默认2)
// 给「我的」导航项添加红点徽章(无数字)
val mineMenuId = R.id.nav_mine
val dotBadge = bottomNav.getOrCreateBadge(mineMenuId)
dotBadge.isVisible = true
dotBadge.number = 0 // 数字为0时显示红点
dotBadge.badgeRadius = resources.getDimensionPixelSize(R.dimen.badge_radius) // 红点大小
在 res/values/dimens.xml 中定义徽章大小:
<dimen name="badge_radius">8dp</dimen>
5.3 其他常用配置
// 1. 设置标签显示模式(labelVisibilityMode)
// labeled: 始终显示图标+文字(默认)
// unlabeled: 始终只显示图标
// selected: 仅选中项显示文字,其他项隐藏文字
// auto: 自适应(屏幕宽度足够时显示文字,不足时隐藏)
bottomNav.labelVisibilityMode = BottomNavigationView.LABEL_VISIBILITY_SELECTED
// 2. 禁用导航项点击动画(可选)
bottomNav.itemHorizontalTranslationEnabled = false
// 3. 监听选中状态变化(替代 setOnItemSelectedListener)
bottomNav.setOnItemReselectedListener { menuItem ->
// 处理重复点击选中项的逻辑(如刷新页面)
when (menuItem.itemId) {
R.id.nav_home -> println("首页被重复选中,执行刷新逻辑")
}
}
// 4. 动态添加/移除导航项
// 添加项
val menu = bottomNav.menu
menu.add(Menu.NONE, R.id.nav_new, Menu.NONE, "新功能")
.setIcon(R.drawable.ic_new)
// 移除项
menu.removeItem(R.id.nav_new)
// 5. 动态修改图标/文字
val homeItem = menu.findItem(R.id.nav_home)
homeItem.icon = resources.getDrawable(R.drawable.ic_home_new, theme)
homeItem.title = "首页2.0"
三、常见问题与解决方案
1. 导航项超过3个时文字被隐藏?
- 原因:默认
labelVisibilityMode为auto,屏幕宽度不足时自动隐藏未选中项文字 - 解决方案:设置
app:labelVisibilityMode="labeled"强制显示文字,或减少导航项数量(建议3-5个)
2. 选中态颜色不生效?
- 检查是否同时设置了
app:itemIconTint和app:selectedIcon,优先级:selectedIcon>itemIconTint - 确保颜色资源是
color类型(而非drawable)
3. Fragment 切换时重复创建?
- 问题:每次切换都
new Fragment()会导致重复创建,数据丢失 - 解决方案:提前初始化 Fragment 实例(如示例中全局变量),复用实例
4. 徽章不显示?
- 检查
badge.isVisible是否设为true - 确保
menu item的id正确匹配 - Material Components 版本需 ≥ 1.1.0(低版本不支持徽章)
四、最佳实践
- 导航项数量控制在3-5个,过多会导致点击区域过小
- 图标建议使用矢量图(Vector Drawable),确保适配不同分辨率
- 选中态与未选中态的图标/颜色对比明显,提升辨识度
- 配合
Fragment切换时,使用replace而非add,避免界面重叠 - 复杂场景(如底部导航+侧边栏)可结合
Navigation Component实现更优雅的导航管理
五、扩展:结合 Navigation Component 使用
如果项目使用 Jetpack Navigation 组件,可通过 NavController 简化 Fragment 切换逻辑:
- 在布局中添加
NavHostFragment:
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"/>
- Kotlin 代码绑定:
val navController = findNavController(R.id.nav_host_fragment)
bottomNav.setupWithNavController(navController)
无需手动处理 Fragment 切换,Navigation 会自动根据 menu 与 nav_graph 的 id 匹配导航逻辑。
3万+

被折叠的 条评论
为什么被折叠?



