底部导航栏
前言
这个专栏系列的文章,是对过去3年工作经验的总结。
希望以后在遇到类似的业务需求时,能直接复制粘贴搞定。
节省下时间去做其它事情。
简介
本文主要记录如何使用BottomNavigationView + ViewPager,来进行底部导航栏结构的页面的实现。
本文没有在底部导航栏进行未读红点、未读数量的功能实现。
因为有印象的一次,是使用TabLayout在顶部进行实现。
但在写文章的过程中,查阅了一些文章和源代码,自己也实践了一下。发现BottomNavigationView比较难实现。
因此有这方面需要的,可以使用BottomNavigationBar、TabLayout、或者开源框架:BottomNavigation (github.com/Ashok-Varma...
- BottomNavigationBar实现Android特色底部导航栏:blog.csdn.net/pengjunhong...
- BottomNavigationBar------底部导航栏控件:blog.csdn.net/A125679880/...
- Android底部导航栏之BottomNavigationBar:www.jianshu.com/p/d6383f5dc...
- 五种方式实现Android底部导航栏:segmentfault.com/a/119000000...
BottomNavigationView + ViewPager
具备功能:
- 选中与未选中状态下的:
- 图标
- 图标颜色
- 文本大小
- 文本颜色
- 文本字体
- 水波纹
效果图:
代码实现
- 文本strings.xml:
XML
<resources>
<!--省略其它代码-->
<string name="home">Home</string>
<string name="find">Find</string>
<string name="mine">Mine</string>
</resources>
- 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>
- 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>
- 文本的字体、颜色、大小的定义:(代码写在: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>
- 页面布局:
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>
- 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>
- 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
}
}
- 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 {}
}
}
}
- AndroidManifest.xml配置:
XML
<activity
android:name=".home.bottomNav.HomeBottomNavActivity"
android:configChanges="keyboardHidden|screenSize|orientation"
android:exported="false"
android:screenOrientation="portrait"/>
- 常用属性说明:
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文件(文字和图片都写在这个里面)
如果想看更多属性,可看参考文章:
- 底部菜单控件BottomNavigationView的使用: blog.csdn.net/eyishion/ar...
- BottomNavigationView底部导航去除缩放动画以及点击水波纹动画: blog.csdn.net/u013293125/...