Android--BottomNavigationView的使用

4,518 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

概述

BottomNavigationView可以用来设置页面的底部菜单栏,一般在底部菜单栏中有3到5个元素的时候推荐使用这个View。使用这个View配合Fragment实现页面的切换效果。同时也可以配合CoordinatorLayout中的部分behavior实现页面滚动的时候隐藏菜单栏,从而能够让屏幕显示更多的内容。

使用

基本使用

要想使用BottomNavigationView十分简单,和普通的View是一样的,我们只需要将其定义在页面的底部,唯一不同的是,我们需要向其指定一个menu标签,menu标签中指定了需要显示的导航item。

  1. 如下我们首先定义了一个menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
            android:id="@+id/menu1"
            android:title="menu1"
            android:icon="@drawable/abc_vector_test" />

    <item
            android:id="@+id/menu2"
            android:title="menu2"
            android:icon="@drawable/frank" />
    <item
            android:id="@+id/menu3"
            android:title="menu3"
            android:icon="@drawable/ic_icon1" />
</menu>
  1. 接着我们在页面中使用BottomNavigationView:
    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <com.google.android.material.bottomnavigation.BottomNavigationView
                android:id="@+id/bottom_navigation_view"
                android:layout_width="@dimen/dp_0"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                android:background="@color/color_f54949"
                app:menu="@menu/menu_bottom_navigation_view_test1" />

    </androidx.constraintlayout.widget.ConstraintLayout>

经过上面简单的两步操作,我们就成功定义了一个BottomNavigationView,下面是这样定义之后的效果:

简单定义的BottomNavigationView

通过上面的动图可以看到: 我们定义了三个用于导航的item,这个三个导航item可以在点击的时候切换到不同的Fragment(尚未实现),而且我们定义的BottomNavigationView本身就拥有点击和切换的效果,这是组件默认实现的效果。

监听切换事件

使用BottomNavigationView主要就是为了配合Fragment进行页面的导航和切换,因此BottomNavigationView向我们提供了setOnNavigationItemSelectedListenersetOnNavigationItemReselectedListener这两个回调来通知我们当前选中的item,我们可以根据这两个回调来进行不同页面的切换。下面的代码简单演示了如何使用这两个回调:

        mBinding.bottomNavigationView.setOnNavigationItemSelectedListener {
            Logs.e(TAG,"item select listener:$it")
            true
        }

        mBinding.bottomNavigationView.setOnNavigationItemReselectedListener {
            Logs.e(TAG,"item reselect listener:$it")
        }

需要注意的是: OnNavigationItemSelectedListener这个接口需要我们返回一个布尔值,这个布尔值表示当前的item是否能够起作用,在上面的代码中我们默认返回true表示任何item都可以起作用,当我们点击BottomNavigationView的某一个item的时候,我们将会看到如下的打印信息:

myStudyProject_system_view.BottomNavigationViewActivity: item reselect listener:menu1
myStudyProject_system_view.BottomNavigationViewActivity: item select listener:menu2
myStudyProject_system_view.BottomNavigationViewActivity: item reselect listener:menu2
myStudyProject_system_view.BottomNavigationViewActivity: item select listener:menu3
myStudyProject_system_view.BottomNavigationViewActivity: item reselect listener:menu3
myStudyProject_system_view.BottomNavigationViewActivity: item select listener:menu1
myStudyProject_system_view.BottomNavigationViewActivity: item reselect listener:menu1

从打印和信息和接口的名称也可以看出这两个回调的区别:

  1. 如果当前点击的item和上一次点击的item(即已经选中的item)不一样,那么将会回调OnNavigationItemSelectedListener这个接口
  2. 如果当前点击和item和上一次点击的item(即已经选中的item)一样,则会回调OnNavigationItemReselectedListener接口

现在很多应用在点击底部导航栏同一个item的时候会刷新页面的数据,这个功能我们通过OnNavigationItemReselectedListener也可以实现。

下面的代码演示了使用BottomNavigationView配合Fragment做页面切换的操作,点击同一个item会刷新页面数据,点击不同的item会切换Fragment:

  1. 首先简单定义三个Fragment:
class BottomNavigationViewMenuFragment: Fragment() {

    private var mBinding: FragmentBottomNavigationViewMenuBinding? = null

    private var mContent: String? = null

    private val mHandler = Handler()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        mBinding = FragmentBottomNavigationViewMenuBinding.inflate(inflater,container,false)
        mBinding?.let {
            if(!TextUtils.isEmpty(mContent)){
                it.tvContent.text = mContent
            }
        }
        return mBinding!!.root
    }

    /**
     * 设置内容
     * @param content 设置要显示的内容
     */
    fun setContent(content: String){
        this.mContent = content
        mBinding?.let {
            it.tvContent.text = mContent
        }
    }

    /**
     * 刷新页面数据
     */
    fun refreshContent(){
        mBinding?.let {
            mHandler.removeCallbacksAndMessages(null)
            it.tvContent.text = "正在刷新"
            mHandler.postDelayed({
                 it.tvContent.text = mContent
            },1000)
        }
    }
}
  1. 这个三个Fragment只是三个不同的实例,我们在Activity中初始化三个Fragment,如下所示:
    private val mFragment1 by lazy {
        BottomNavigationViewMenuFragment().apply {
            this.setContent("menu1")
        }
    }

    private val mFragment2 by lazy {
        BottomNavigationViewMenuFragment().apply {
            this.setContent("menu2")
        }
    }

    private val mFragment3 by lazy {
        BottomNavigationViewMenuFragment().apply {
            this.setContent("menu3")
        }
    }
  1. 当我们重复点击某一个导航item的时候,Fragment中会执行刷新的操作,否则执行切换Fragment的操作:
        mBinding.bottomNavigationView.setOnNavigationItemSelectedListener {
            Logs.e(TAG,"item select listener:$it")
            when(it.itemId){
                R.id.menu1 ->
                    switchFragment(mFragment1)
                R.id.menu2 ->
                    switchFragment(mFragment2)
                R.id.menu3 ->
                    switchFragment(mFragment3)
            }

            true
        }

        mBinding.bottomNavigationView.setOnNavigationItemReselectedListener {
            Logs.e(TAG,"item reselect listener:$it")
            if(mCurrentFragment is BottomNavigationViewMenuFragment){
                (mCurrentFragment as BottomNavigationViewMenuFragment).refreshContent()
            }
        }

运行上面的程序可以得到如下的效果:

BottomNavigationView配合Fragment实现页面导航

其它属性

系统为我们默认提供的是基于主题色的属性,很多时候这些属性的设置并不能满足我们的需求,BottomNavigationView也为我们提供了部分字段来实现自定义的需求,如下表所示:

属性名
属性简介
备注
itemIconSize这个属性用于设置icon的大小需要注意的是,并不能通过修改这个属性的值来修改BottomNavigationView的高度,目前来看BottomNavigationViewmenu的高度是固定的,并不会随着icon的高度变化而变化,icon设置的较大可能还会导致文本看的不清楚
itemRippleColor点击item时水波纹的颜色这个属性只有在没有设置itemBackground的时候才有用
itemIconTint这个属性用于设置图标的颜色这里我们可以通过设置一个ColorStateList来指定在不同状态下图标的颜色,测试发现通过设置的不同状态下的图片在切换的时候可能没有效果
itemBackground设置导航item的背景颜色这只此项可能会导致点击时的水波纹效果消失
itemTextColor设置导航栏item文本颜色这里仍然可以通过设置一个ColorStateList来指定不同状态下文本的颜色
itemHorizontalTranslationEnabled设置当某一个item被选中的时候别的item的情况如果设置为true表示当某一个item被选中缩放的时候其它的item会被横向挤压,如果设置为false表示当某一个item被选中缩放的时候,别的item不会被挤压
itemTextAppearanceInactive这个属性不推荐使用应该使用itemTextColor并传递ColorStateList设置文本的颜色
itemTextAppearanceActive这个属性不推荐使用应该使用itemTextColor并传递ColorStateList设置文本的颜色
labelVisibilityMode设置标签的显示模式auto表示根据当前的子项的数量自由决定是否显示标签,selected表示导航项被选中的时候显示标签,unlabeled表示标签任何时候都不会显示,labeled表示标签在任何时候都会显示

设置角标

BottomNavigationView允许我们给某一个导航项设置角标,我们可以指定角标的数量和位置,背景颜色等。使用角标我们可以很容易地提醒用户未读的消息数量等信息,这对于那些拥有消息通知功能的应用来说是非常方便的,下面的代码演示如何在某一个导航项上添加角标:

        val badge = mBinding.bottomNavigationView.getOrCreateBadge(R.id.menu3)
        badge.number = 100
        badge.backgroundColor = ContextCompat.getColor(this,R.color.colorPrimaryDark)
        badge.badgeTextColor = ContextCompat.getColor(this,R.color.design_default_color_error)
        badge.badgeGravity = BadgeDrawable.TOP_END
        badge.verticalOffset = 5
        badge.horizontalOffset = 5

在上面的代码中,我们向id为menu3的导航项上添加了一个角标,我们设置了角标上显示的数量为100,同时设置了角标的背景颜色和文本颜色,最后设置了角标位于这个导航项的右上角,同时基于右上角我们设置和横向和纵向的偏移量。

运行上面的程序会得到如下的结果:

设置角标