Android快速开发模板:首页-BottomNavigationView+ViewPager

底部导航栏

前言

这个专栏系列的文章,是对过去3年工作经验的总结。

希望以后在遇到类似的业务需求时,能直接复制粘贴搞定。

节省下时间去做其它事情。

简介

本文主要记录如何使用BottomNavigationView + ViewPager,来进行底部导航栏结构的页面的实现。

本文没有在底部导航栏进行未读红点、未读数量的功能实现。

因为有印象的一次,是使用TabLayout在顶部进行实现。

但在写文章的过程中,查阅了一些文章和源代码,自己也实践了一下。发现BottomNavigationView比较难实现。

因此有这方面需要的,可以使用BottomNavigationBar、TabLayout、或者开源框架:BottomNavigation (github.com/Ashok-Varma...

BottomNavigationView + ViewPager

具备功能:

  • 选中与未选中状态下的:
    • 图标
    • 图标颜色
    • 文本大小
    • 文本颜色
    • 文本字体
  • 水波纹

效果图:

代码实现

  1. 文本strings.xml:
XML 复制代码
<resources>
    <!--省略其它代码-->
    <string name="home">Home</string>
    <string name="find">Find</string>
    <string name="mine">Mine</string>
</resources>
  1. icon资源文件、selector:(以selector_home.xml示例,其余的自己补充)
XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_home_blue" android:state_selected="true"/>
    <item android:drawable="@drawable/ic_home"/>
</selector>
  1. menu:
XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
            android:id="@+id/first"
            android:icon="@drawable/selector_home"
            android:title="@string/main"/>
    <item
            android:id="@+id/two"
            android:icon="@drawable/selector_find"
            android:title="@string/find"/>
    <item
            android:id="@+id/three"
            android:icon="@drawable/selector_mine"
            android:title="@string/mine"/>
</menu>
  1. 文本的字体、颜色、大小的定义:(代码写在:style.xml)
XML 复制代码
<resources>
    <!--省略其它代码-->
    <style name="nav_active_style" parent="TextAppearance.AppCompat">
        <item name="fontFamily">sans-serif-black</item>
        <item name="android:textSize">16sp</item>
        <item name="android:textColor">#5694FF</item>
    </style>
    <style name="nav_in_active_style" parent="TextAppearance.AppCompat">
        <item name="fontFamily">sans-serif-thin</item>
        <item name="android:textSize">12sp</item>
    </style>
</resources>
  1. 页面布局:
xml 复制代码
<?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"
>

  <androidx.viewpager.widget.ViewPager
          android:id="@+id/vp"
          android:layout_width="match_parent"
          android:layout_height="0dp"
          app:layout_constraintBottom_toTopOf="@id/bnv"
          app:layout_constraintTop_toTopOf="parent"
  />

  <com.google.android.material.bottomnavigation.BottomNavigationView
          android:id="@+id/bnv"
          android:layout_width="match_parent"
          android:layout_height="56dp"
          android:background="?android:attr/windowBackground"
          app:itemIconSize="27dp"
          app:itemTextAppearanceActive="@style/nav_active_style"
          app:itemTextAppearanceInactive="@style/nav_in_active_style"
          app:layout_constraintBottom_toBottomOf="parent"
          app:menu="@menu/home_bottom_nav_menu"
  />

</androidx.constraintlayout.widget.ConstraintLayout>
  1. Fragment布局:(用于ViewPager进行显示)
xml 复制代码
<?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"
>

    <TextView
            android:id="@+id/tvFmName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Common Fragment"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
    />

</androidx.constraintlayout.widget.ConstraintLayout>
  1. Fragment:
Kotlin 复制代码
class CommonFragment: BaseFragment<CommonFragmentBinding, ViewModel>() {

    private var mNeedShowText: String = ""

    fun setText(text: String) {
        mNeedShowText = text
    }

    override fun getViewModel(): Class<ViewModel> {
        return ViewModel::class.java
    }

    override fun onCreateBinding(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): CommonFragmentBinding {
        return CommonFragmentBinding.inflate(inflater, container, false)
    }

    override fun initView() {
        mBinding.tvFmName.text = mNeedShowText
    }
}
  1. Activity:
Kotlin 复制代码
class HomeBottomNavActivity: BaseActivity<HomeBottomNavActivityBinding, ViewModel>() {

    companion object {
        fun start(context: Context) {
            val intent = Intent(context, HomeBottomNavActivity::class.java)
            context.startActivity(intent)
        }
    }

    private var mFirstFragment: CommonFragment? = null
    private var mSecondFragment: CommonFragment? = null
    private var mThirdFragment: CommonFragment? = null
    private var mFragments = emptyList<Fragment>()

    private val mFragmentIds by lazy {
        arrayListOf<Int>().also {
            it.add(R.id.first)
            it.add(R.id.two)
            it.add(R.id.three)
        }
    }

    override fun getViewBinding(): HomeBottomNavActivityBinding {
        return HomeBottomNavActivityBinding.inflate(layoutInflater)
    }

    override fun getViewModel(): Class<ViewModel> {
        return ViewModel::class.java
    }

    override fun initView() {
        initFragments()
        initViewPager()
        initNavigation()
    }

    override fun initListener() {
    }

    private fun initFragments() {
        mFirstFragment = CommonFragment().apply { setText("第1个Fragment") }
        mSecondFragment = CommonFragment().apply { setText("第2个Fragment") }
        mThirdFragment = CommonFragment().apply { setText("第3个Fragment") }
        mFragments = mutableListOf(mFirstFragment!!, mSecondFragment!!, mThirdFragment!!)
    }

    private fun initViewPager() {
        mBinding.vp.adapter = object : FragmentPagerAdapter(supportFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            override fun getCount(): Int {
                return mFragments.size
            }

            override fun getItem(position: Int): Fragment {
                return mFragments[position]
            }
        }

        mBinding.vp.offscreenPageLimit = mFragments.size
        mBinding.vp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {}

            override fun onPageSelected(position: Int) {
                /*
                页面切换:
                根据ViewPager的页面切换,更新BottomNavigationView的选中项。
                如果要禁止ViewPager的左右滑动,那么继承ViewPager,并在onTouch、onInterceptTouchEvent方法中,
                返回false即可。
                 */
                mBinding.bnv.menu[position].isChecked = true
            }

            override fun onPageScrollStateChanged(state: Int) {}

        })
    }

    private fun initNavigation() {
        // 去掉图标的遮罩颜色
        mBinding.bnv.itemIconTintList = null
        mBinding.bnv.apply {
            // 由未选中到选中状态时
            setOnNavigationItemSelectedListener {
                mFragmentIds.indexOf(it.itemId).also { index ->
                    if (index < 0 || (mBinding.vp.adapter?.count ?: 1) <= index) return@also
                    // ViewPager切换到对应的页面
                    mBinding.vp.currentItem = index
                }
                true
            }

            // 选中状态再次点击时触发
            setOnNavigationItemReselectedListener {}
        }
    }
}
  1. AndroidManifest.xml配置:
XML 复制代码
<activity
        android:name=".home.bottomNav.HomeBottomNavActivity"
        android:configChanges="keyboardHidden|screenSize|orientation"
        android:exported="false"
        android:screenOrientation="portrait"/>
  1. 常用属性说明:
Kotlin 复制代码
app:itemTextColor 文字的颜色,可以通过selector来控制选中和未选中的颜色
app:itemIconTint 图标的颜色,可以通过selector来控制选中和未选中的颜色
app:itemIconSize 图标大小,默认24dp
app:iteamBackground 背景颜色,默认是主题的颜色
app:itemRippleColor 点击后的水波纹颜色
app:itemTextAppearanceActive 设置选中时文字样式
app:itemTextAppearanceInactive 设置默认的文字样式
app:itemHorizontalTranslationEnabled 在label visibility 模式为selected时item水平方向移动
app:elevation 控制控件顶部的阴影
app:labelVisibilityMode 文字的显示模式
app:menu 指定菜单xml文件(文字和图片都写在这个里面)

如果想看更多属性,可看参考文章:

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui