前言
tab选项、底部或者顶部的导航栏、菜单栏 点击时都需要去切换fragment页面,可以有以下实现方式:
- 可以使用FrameLayout控件手动切换(不可滑动,滑动需单独实现)
- 使用viewpage2自动切换(可以滑动)
一、问题
ViewPage2是Jetpack中的其中一个组件,可以实现滑动切换页面的效果,通常可以搭配其他组件实现滑动切换效果。ViewPager2是基于RecyclerView实现的,自然继承了RecyclerView的众多优点,并且针对ViewPager存在的问题做了优化。可以使用offscreenPageLimi属性来预先加载页面,也可设置为0实现懒加载(页面显示时再加载)。
但是在实际开发中遇到一个问题:
ViewPager2下多个fragment的页面高度可能是不同的,在允许预加载的条件下ViewPager2的高度会始终跟随最高的那个fragment的高度,导致其他比较低的fragment页面出现留白的问题。即便禁止了预加载,也不能解决。
二、解决
1. ViewPage2添加配置
如果要实现ViewPage2的高度始终和当前的fragment保持一致,那么就需要在每一次点击tab的时候重新计算当前fragment的高度并将高度设置给ViewPage2。
vp2_frag是ViewPage2控件,fragmentList是所有fragment的集合
private val fragmentList = arrayListOf<Fragment>()
......
//viewPager初始化以及切换tab的时候都会触发这个监听,不管是否禁止了预加载都不会有影响
vp2_frag.viewTreeObserver.addOnGlobalLayoutListener {
if(fragmentList.size>0){
updatePagerHeightForChild(fragmentList[vp2_frag.currentItem].view, vp2_frag)
}
}
vp2_frag.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback(){
override fun onPageSelected(position: Int) {
//切换页面效果
super.onPageSelected(position)
updatePagerHeightForChild(fragmentList[vp2_frag.currentItem].view, vp2_frag)
}
})
//自适应高度,计算fragment的高度并设置给viewPager
private fun updatePagerHeightForChild(view: View?, pager: ViewPager2) {
view?.post {
//设置尺寸和模式的测量规则
val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
view.measure(wMeasureSpec, hMeasureSpec)
if (pager.layoutParams.height != view.measuredHeight) {
pager.layoutParams = pager.layoutParams.also {
lp ->
lp.height = view.measuredHeight
}
}
}
}
不允许预加载时,点击tab的时候onItemSelect方法可能会比创建Fragment更早的执行,导致拿不到fragment。
允许预加载之后,ViewPage2加载完成后它的高度仍然跟随最高的那个fragment,点击tab重新计算高度的代码没有执行,从而导致第一个fragment可能留白。
所以需要在一个可以监听到ViewPage2初始化又可以监听到ViewPage2切换的地方调用updatePagerHeightForChild方法。
2. 换成手动切换
使用FrameLayout去手动切换,重新加载页面:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginBottom="0dp"/>
在activity中使用:R.id.content是FrameLayout