Android-FragmentPagerAdapter刷新无效的解决方案

最近在重构项目的时候有个地方想要做一个更换FragmentPagerAdapter中的Fragment的功能,按照通常使用ListView的习惯做法,如果你只是更新保存Fragment的List数据,然后调用adapter的notifyDataSetChanged()是不会起作用的(下面会分析原因)。
搜索了下发现此问题普遍存在,多数是说先移除Fragment再notifyDataSetChanged(),因为FragmentPagerAdapter内部会缓存Fragment,但是经测试发现仅仅这样干是不行的。于是经过一番折腾,参考了各种方案之后我整理了一个可行的方案,本文做一个记录,以便后续参考,也方便各位道友参考。

下面来分析一下此问题的主要原因:

这可能是Android一个BUG, 与此问题相关的主要有两个方法:

  • getItemPosition()
  • instantiateItem()

搞清楚这两个方法的作用基本就知道如何解决了,先来看第一个方法
getItemPosition()

这个是在PagerAdapter中的getItemPosition()源码的说明,从它的英文注释我们可以清楚的知道,这个方法的返回值的意思是:如果给定的item的position没有发生改变,那么就返回POSITION_UNCHANGED, 如果给定的item在adapter当中指定位置不再呈现了,那么就返回POSITION_NONE。默认返回的是POSITION_UNCHANGED

OK, 导致这个问题的一个主要原因我们已经知道了,所以,默认我们是要重写这个方法的,不然总是返回POSITION_UNCHANGED,那当然是不会更新的了

其实在使用viewpager包含普通view界面的时候我们应该会经常遇到这个问题的。那么, 这个问题的解决思路就有了:
我们就按照它要求的意思来实现当position发生变化的item我们都返回POSITION_NONE,而position没有发生变化的item我们就返回POSITION_UNCHANGED

那怎么来实现呢,我们简单来想一下,首先我要记录更新之前的每个item对应的position,然后在更新Fragment列表数据之后,我们再把当前的每一个item的position跟之前的去比对一遍,这样我们就能知道到底哪个item的position发生了变化,哪个的position依然没变了。当然前提是比对的item是相同的item, 如果更新之后item都不存在了,那自然要返回POSITION_NONE了。
好,我们这里就简单的思路设想一下,后面我会给出完整代码。

到这里包含普通view的viewpager的adapter刷新问题应该可以解决了,注意,这里很多人的暴力做法是在getItemPosition()当中直接返回POSITION_NONE,这样不是不可以,只不过这样做会默认把所有的view都重新销毁重建,那肯定不是我想要的理想的情况。

接下来再看另一个方法:
instantiateItem()
这个是在FragmentPagerAdapter的源码当中的,可以看到在instantiateItem()方法的内部,它是这样做的:根据tag查找对应的Fragment, 如果找到,那么就通过当前的Transaction进行attach操作,这个fragment就会显示了,如果没有找到呢,就去getItem()从你的Fragment列表中获取一个然后Transaction进行add操作。

所以看到这里就恍然大悟了,为啥我list里面的fragment都换了新的了但就是死活不刷新呢,罪魁祸首就在这里了,只要它能findFragmentByTag找得到那么就不会用你的列表中的fragment, 还是用之前的。

那么,到这里首先想到的就是,我们在更换或者删除列表中对应的Fragment时,同时也要将该Fragment从Transaction当中移除,这样就能够确保在刷新数据时adpater会从我们更新后的list中去获取fragment而不是用之前缓存的。

是不是这样?对不对?嗯,应该是没有问题的,好,想到这里那么我们就可以撸起袖子动手干了,加上前面getItemPosition()的思路,应该是能够解决问题的了。假设你按照前面的思路完善了FragmentPagerAdapter的代码并准备测试(或者你可以直接往下拖查看完整的代码),你会悲剧的发现,在更换某一个fragment的时候是没有问题的,但是在删除某一个fragment时是会出现问题的,会发生crash! 抛出如下异常:
这里写图片描述

这里写图片描述

哎,没办法,江湖就是如此险恶,到处都是坑。。
那么究竟为什么发生crash呢,如果你查看该crash异常栈,我们可以在源码中搜素一下找到:

这里写图片描述
没错,就是在高亮的这一行,如果你按照前面介绍的方法写好FragmentPagerAdapter 运行测试了,你就会发现抛出”Can’t change tag of fragment “的异常,我们可以发现上述的异常是在beginTransaction()之后进行add操作发生的,异常出现的判断条件是fragment.mTag != null &&!tag.equals(fragment.mTag),这里的tag就是add时传入的tag参数, 而mTag是要添加的frgament的tag, 这说明这个fragment之前被添加过,因为下面一行fragment.mTag = tag;我们知道只有添加过的fragment的mTag才不会为null。

那问题肯定是跟tag有关了,我们回到instantiateItem()方法的源码,可以看到不管是add操作还是findFragmentByTag时的tag都是通过一个方法生成的:
这里写图片描述
这里写图片描述
makeFragmentName(), 都是这个方法生成的tag, 而这个方法生成tag的办法是getItemId()和viewId的组合, viewId应该就是我们的fragment的id了,而getItemId():
这里写图片描述
它默认实现就是简单的返回position,所以tag是由fragment的id+position组成的。
那我们来分析一下,删除的时候为啥会出现”Can’t change tag of fragment “的异常,先画个简图:

这里写图片描述
假设初始时我们viewpager当中有4个Fragment分别是A B C D, 那么按照instantiateItem()源码中的tag生成方法,这四个fragment被add之后对应四个fragment中的mTag值应该分别就是:A0、B1、C2、D3(假设就用ABCD代表他们的fragment的id),好,现在我们把B对应的Fragment删除掉(注意此时我们已经按照前面已发现的解决方案实现了的代码)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值