背景:ViewPager2下不同的tab会对应各自的fragment,往往这些fragment的高度是不同的,然而在允许预加载的条件下ViewPager2的高度会始终跟随最高的那个fragment的高度,这就会导致其他不够高的fragment出现留白的问题。即便禁止了预加载,只要点击tab加载了比当前fragment更高的另一个fragment那么viewPager的高度也会随着改变,就仍然会导致上一个fragment出现大量留白的问题。如下所示:

iOS的viewPager怎么实现_iOS的viewPager怎么实现

 思考:首先我们想要的效果是viewPager的高度始终和当前的fragment保持一致,那么就需要在每一次点击tab的时候重新计算当前fragment的高度并将高度设置给viewPager,代码如下:

...
    override fun createFragment(position: Int): Fragment {
        val fragment: Fragment = AFragment()
        fragmentList.add(position, fragment)//声明一个Fragment类型的集合来作为缓存区
        return fragment
    }
...

hotSpotsTabLayout?.setOnItemSelectListener(object :
    HotSpotsTabLayout.OnItemSelectListener {
        override fun onItemSelect(text: String?, index: Int) {
            binding.viewPager.currentItem = index
            updatePagerHeightForChild(fragmentList[index].view, binding.viewPager)
        }
})

//计算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
                }
        }
    }
}

如果当前viewPager禁止预加载,那么在你点击tab的时候onItemSelect方法会比createFragment方法更早的执行,这就意味着在onItemSelect方法中通过fragmentList[index].view去拿下一个Fragment是拿不到的,因为根本就还没有add进去甚至都还没有创建,然后就会报数组越界的问题。

如果允许预加载,上述的问题就不存在了,点击tab后viewPager的高度都可以跟着变化。但是,你会发现在不点击任意tab的时候展示的第一个fragment的高度也会留白(除非第一个fragment碰巧就是最高的那个),只有点击其他tab再点回来它的高度才会自适应。这是因为允许预加载之后viewPager在加载完成时它的高度仍然跟随最高的那个fragment,点击tab重新计算高度的逻辑并没有走。那么在createFragment方法中也调用一次updatePagerHeightForChild方法呢?这样是无效的,因为fragment并没有完成绘制,还不在viewTree中,即便fragment已经创建你拿到的fragmentList[index].view仍然是null值,有兴趣的小伙伴可以试试。

解决:所以我们需要在一个可以监听到viewPager初始化同时又可以监听到viewPager点击tab进行切换的地方调用updatePagerHeightForChild方法,能立刻想到的就是用ViewTreeObserver去注册viewPager的OnGlobalLayoutListener监听,你会发现viewPager初始化以及切换tab的时候都会触发这个监听,不管是否禁止了预加载都不会有影响:

binding.viewPager.viewTreeObserver.addOnGlobalLayoutListener {
    updatePagerHeightForChild(fragmentList[binding.viewPager.currentItem].view, binding.viewPager)
}

问题完美解决~