Android Viewpager2 remove fragmen不生效解决方案

时间:2024-07-15 09:04:39

一、介绍

在如今的开发过程只,内容变化已多单一的fragment,变成连续的,特别是以短视频或者直播为主的场景很多。从早起的Viewpage只能横向滑动,到如今的viewpage2可以支持横向或者竖向滑动。由于viewpage2的adapter在设计时支持缓存,导致想立马生效出现问题,不符合国内的业务场景。

二、viewpage2+FragmentStateAdapter设计原理分析

1.Viewpager2

Viewpaer2的设计和viewpage还是有区别的,最大的区别是viewpage是基础viewgroup,通过scroll控制整体view的滑动,在早起的时候,很多都是可以通过adapter去自定义缓存,但是viewpage2在androidx中新增的,是通过对recycleview进行二次封装出来的一个新业务。

从源码中可以看出,核心是recycleview,这个控件在之前v7包中是独立出来的,相对listview性能更好,在缓存和使用更流畅,也是支持横向或者竖向滑动。

2.FragmentStateAdapter

adpter的与Recycview.adapter还是有区别的。核心是adapter和holder。

2.1FragmentStateAdapter

继承recycView.adapter,里面对item进行了缓存,mFragments是一个key和fragment绑定的关系,下表就是fragment的索引。如果不经常对fragment移除,那么这个缓存可以大大提高性能。但是也就是这个原因,导致在设计的时候没考虑到移除立马生效等问题

2.2FragmentViewHolder

继承了RecycleView.holder,只要是在holder阶段,创建一个rootview->FragmentLayout将fragment包进来。提供容器

三、删除无法立即更新分析

通过第二段,了解了viewpage2+FragmentStateAdapter的设计,可以了解到这些设计的目的。但是我们在开发过程中的场景比较复杂,有人习惯了recycleView+recycleView.Adapter,以为viewpage2的核心也是这套,删除数据或者更新数据直接通过notify去处理,结果发现viewpage2移除不是我们要的那个索引,这是为什么呢?

1.问题分析:

这个问题和fragmentStateAdapter设计有关,在这个adapter中,mfragment的缓存是通过下表缓存的,也就是我们虽然把数据移除了,但是position在adapter的索引是连续的,还是从0开始,一直到最后一个元素,就算我们通知了notifyItemRemoved(position),但是数据移除了,下标也发生了变化,这时候我们通知移除的变成了当前位置后一个:position+1,和我们理想中还是有比较大的区别

这种做法和recycleView.Adapter内部不一样,很多开发人员遇到确实无法处理,想着从数据来处理,这种方法是行不通的。

四、解决方案

目前暴露的api还是很不好处理,网上的方案也是五花八门

1.重置adapter:

        这种做法是删除数据,把viewpage2的adapter设为null,再用数据重新生成一个新的,这样做的弊端是影响了性能和体验

2.直接notify刷新:

        发现下标索引乱了,数据移除失败

处理这个问题的核心是要把mfragment数组中的要对应的数据下表给移除,然后重新排序。只有保持索引下表和mfragment中的fragment对应,才能取到我们想要的view。

在adapter中也提供了一个remove的方法:removeFragment(position:Int),但是这个方法是私有的,我们只能通过反射来获取这个方法

步骤:

1.先将数据中索引下的数据移除

2.在移除removeFragment,最后在notifyItemRemoved刷新列表,保持索引的真实性

        public fun remove(position: Int) {
            //先移除item在父类中的adapter
            adapter?.apply {
                val cls = this.javaClass
                val method = cls.superclass.getDeclaredMethod("removeFragment", Long::class.java)
                method.isAccessible = true
                method.invoke(this, position)
            }
            if (position == viewPager!!.currentItem) {
                viewPager?.setCurrentItem(position, false)
            }

            mlsit.removeAt(position)
            notifyItemRemoved(position)
        }

只有通过这样,才能保持下标与数据所在数组的准确性。