最近整理项目框架,决定使用BottomNavigationView+Navigation来完成主页,碰到了一系列问题,贡献出摸索过程,供需要的宝宝参考。大体效果如下图:
一、准备
implementation "androidx.navigation:navigation-fragment-ktx:2.3.0"
implementation "androidx.navigation:navigation-ui-ktx:2.3.0"
二、首先解决BottomNavigationView出现的几个问题
- 切换条目出现缩放位移动画
·添加app:labelVisibilityMode="labeled" - 点击item怎么去掉水波纹动画
·添加app:itemBackground="@null" - 自定义item图片后没有显示自己想要的图片
·activity中设置bottom_nav.itemIconTintList = null - 字体大小怎么修改
·dimen文件中直接覆盖
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">12sp</dimen>
<dimen name="design_bottom_navigation_text_size" tools:override="true">12sp</dimen>
三、实现
这种底部导航有两种实现方式。
第一种简单方式,直接使用fragment节点
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/main"
tools:layout="@layout/fragment_home" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:itemBackground="@null"
app:itemIconTint="@null"
app:itemTextColor="@color/selector_main_color"
app:labelVisibilityMode="labeled"
app:menu="@menu/bottom_nav" />
</LinearLayout>
这里需要注意的是bottom_nav中item的id要同导航文件中fragment的id相同,否则找不到id不能切换页面
在activity文件中添加一行代码就能顺利切换页面了
bottom_nav.setupWithNavController(findNavController(R.id.nav_host_fragment))
但
是
,
使
用
这
种
方
式
切
换
页
面
有
两
个
缺
点
:
\color{#FF0000}{但是,使用这种方式切换页面有两个缺点:}
但是,使用这种方式切换页面有两个缺点:
1.每次切换页面都重新走一遍fragment生命周期
2.页面点击返回后返回到了navigation中设置的startDestination,但是我想要的是在任一页面按返回都直接退出APP
然后,就出现了第二种方式,使用FragmentContainerView
<LinearLayout 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"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:itemBackground="@null"
app:itemIconTint="@null"
app:itemTextColor="@color/selector_main_color"
app:labelVisibilityMode="labeled"
app:menu="@menu/bottom_nav" />
</LinearLayout>
这种方式需要自己写切换页面逻辑,也就是使用fragmentTransaction的show和hide方法,这样,fragment生命周期就不会每次都重新走一遍了,在任一页面退出都能退出APP。
切换逻辑如下:(注意别忘了每个导航页面的id要同menu中的item id保持一致)
fun BottomNavigationView.setupWithNavController(
navGraphIds: List<Int>,
fragmentManager: FragmentManager,
containerId: Int
): LiveData<NavController> {
// Result. Mutable live data with the selected controlled
val selectedNavController = MutableLiveData<NavController>()
if (navGraphIds.isEmpty()) return selectedNavController
var selectedItemTag = getFragmentTag(selectedItemId)
fragmentManager.beginTransaction()
.add(containerId, NavHostFragment.create(navGraphIds[0]), selectedItemTag)
.commitNowAllowingStateLoss()
setOnNavigationItemSelectedListener { item ->
if (fragmentManager.isStateSaved) {
false
} else {
val newlySelectedTag = getFragmentTag(item.itemId)
if (selectedItemTag != newlySelectedTag) {
val selectedFragment =
fragmentManager.findFragmentByTag(selectedItemTag) as NavHostFragment?
val fragmentTransaction = fragmentManager.beginTransaction()
selectedFragment?.let { fragmentTransaction.hide(it) }
var newlySelectedFragment =
fragmentManager.findFragmentByTag(newlySelectedTag) as NavHostFragment?
if (null == newlySelectedFragment) {
var index = 0
menu.forEachIndexed { index1, item1 ->
if (item1 == item) {
index = index1
return@forEachIndexed
}
}
newlySelectedFragment = NavHostFragment.create(navGraphIds[index])
fragmentTransaction
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim
)
.add(containerId, newlySelectedFragment!!, newlySelectedTag)
.commitNowAllowingStateLoss()
} else {
fragmentTransaction.show(newlySelectedFragment).commitNowAllowingStateLoss()
}
selectedItemTag = newlySelectedTag
selectedNavController.value = selectedFragment?.navController
true
} else {
false
}
}
}
return selectedNavController
}
activity直接设置即可
val navGraphIds = listOf(
R.navigation.home,
R.navigation.sort,
R.navigation.find,
R.navigation.cart,
R.navigation.mine
)
bottom_nav.setupWithNavController(
navGraphIds = navGraphIds,
fragmentManager = supportFragmentManager,
containerId = R.id.nav_host_container
)
此文只是简单使用navigation实现底部导航栏小功能。导航组件还有很多需要学习的地方,但是也有很多需要改进的地方,希望不久的将来我们都能开发出单activity的APP。
另外,整理项目框架的时候顺便从dagger2迁移到了hilt,虽然hilt还是开发版本。hilt确实比dagger使用方便了很多,但是要理解透彻也是不太容易,毕竟依赖注入对于很多人而言就已经很难理解了 (T_T)
MVVM+ViewModel+Databinding+retrofit+rxJava+BottomNavigationView简单框架