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文件(文字和图片都写在这个里面)

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

相关推荐
weixin_4493108411 分钟前
高效集成:聚水潭采购数据同步到MySQL
android·数据库·mysql
Zender Han26 分钟前
Flutter自定义矩形进度条实现详解
android·flutter·ios
白乐天_n2 小时前
adb:Android调试桥
android·adb
姑苏风6 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
数据猎手小k10 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小1010 小时前
JavaWeb项目-----博客系统
android
风和先行11 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.11 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰12 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶12 小时前
Android——网络请求
android