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

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

相关推荐
西瓜本瓜@11 分钟前
在Android中fragment的生命周期
android·开发语言·android studio·kt
老哥不老2 小时前
MySQL安装教程
android·mysql·adb
xcLeigh3 小时前
html实现好看的多种风格手风琴折叠菜单效果合集(附源码)
android·java·html
图王大胜4 小时前
Android SystemUI组件(07)锁屏KeyguardViewMediator分析
android·framework·systemui·锁屏
InsightAndroid4 小时前
Android通知服务及相关概念
android
aqi006 小时前
FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播
android·ffmpeg·音视频·直播·流媒体
Leoysq6 小时前
Unity实现原始的发射子弹效果
android
起司锅仔6 小时前
ActivityManagerService Activity的启动流程(2)
android·安卓
猿小蔡7 小时前
Android Bitmap 和Drawable的区别
android
峥嵘life7 小时前
Android14 手机蓝牙配对后阻塞问题解决
android·智能手机