Android BottomNavigationView 介绍

BottomNavigationView 是 Android Support Library(现为 AndroidX)提供的底部导航组件,用于实现应用底部的tab切换功能,适用于3-5个导航项的场景,符合 Material Design 设计规范,能提升用户操作便捷性。

一、核心特性

  1. 支持图标+文字/纯图标两种显示模式
  2. 自带选中态切换动画(颜色、大小变化)
  3. 支持徽章(Badge)显示未读消息数
  4. 可配置导航项点击事件、选中状态监听
  5. 适配 AndroidX,支持深色模式
  6. 支持自定义图标、文字样式、背景色

二、基础使用步骤

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个时文字被隐藏?

  • 原因:默认 labelVisibilityModeauto,屏幕宽度不足时自动隐藏未选中项文字
  • 解决方案:设置 app:labelVisibilityMode="labeled" 强制显示文字,或减少导航项数量(建议3-5个)

2. 选中态颜色不生效?

  • 检查是否同时设置了 app:itemIconTintapp:selectedIcon,优先级:selectedIcon > itemIconTint
  • 确保颜色资源是 color 类型(而非 drawable

3. Fragment 切换时重复创建?

  • 问题:每次切换都 new Fragment() 会导致重复创建,数据丢失
  • 解决方案:提前初始化 Fragment 实例(如示例中全局变量),复用实例

4. 徽章不显示?

  • 检查 badge.isVisible 是否设为 true
  • 确保 menu itemid 正确匹配
  • Material Components 版本需 ≥ 1.1.0(低版本不支持徽章)

四、最佳实践

  1. 导航项数量控制在3-5个,过多会导致点击区域过小
  2. 图标建议使用矢量图(Vector Drawable),确保适配不同分辨率
  3. 选中态与未选中态的图标/颜色对比明显,提升辨识度
  4. 配合 Fragment 切换时,使用 replace 而非 add,避免界面重叠
  5. 复杂场景(如底部导航+侧边栏)可结合 Navigation Component 实现更优雅的导航管理

五、扩展:结合 Navigation Component 使用

如果项目使用 Jetpack Navigation 组件,可通过 NavController 简化 Fragment 切换逻辑:

  1. 在布局中添加 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"/>
  1. Kotlin 代码绑定:
val navController = findNavController(R.id.nav_host_fragment)
bottomNav.setupWithNavController(navController)

无需手动处理 Fragment 切换,Navigation 会自动根据 menunav_graphid 匹配导航逻辑。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值