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

好,我们这里就简单的思路设想一下,后面我会给出完整代码。

到这里包含普通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删除掉(注意此时我们已经按照前面已发现的解决方案实现了的代码):

这里写图片描述

此时列表中只剩下A C D三个Fragment, 那么前面提到过,此时getItemPpsition()方法我们应该做的是A对应的Fragment返回POSITION_UNCHANGED, 因为A的位置没有发生变化,而B(已删除)、 C(移位) 、 D(移位) 三个我们应该返回POSITION_NONE,因此我们的adapter在刷新的时候刷新到第二个位置时会再首先去查找对应tag的Fragment:

这里写图片描述

此时查找的tag是C1,然而找不到,因为C前面add的tag是C2,所以走else, 在else当中就会从我们的列表中去get第1个item,那取到的自然是C,然后对C进行add操作,这时又会生成C对应的tag传入add()方法,但是此时,注意了,生成C的tag的方法生成的结果是C1(fragment的id+当前position),分析到这里你可能发现了,前面我们的C是被add过的,所以之前C的mTag是C2,到了这里add操作时要变成C1了!所以跟着源码走进去自然就符合前面“Can’t change tag of fragment “异常的判断条件fragment.mTag != null &&!tag.equals(fragment.mTag),我们的C之前的mTag不为空并且C1 != C2,所以中标了!

那么解决问题的方法,首先想到的是为每一个Fragment设置一个唯一的tag值,但是mTag在Fragment源码中是protected的,我们是不能改的。。。所以只能去改生成tag的方法makeFragmentName()了,但是一看这个方法又是private的,又不能改。。。。我TMD…CNM…MMP…好吧,再看,因为makeFragmentName()方法用到了getItemId()的返回值,而getItemId()我们是可以重写的,所以那去只能改getItemId()方法了:

@Override

public long getItemId(int position) {

// return position;

return 我们自定义的可以确定当前item的唯一值;

}

因为前面提到过getItemId()方法默认返回的是position,所以我们这个方法要修改一下,返回一个唯一的值,一个可以标志这个fragment的唯一值就可以了,这样在删除操作position发生变化之后,C的tag值经过makeFragmentName()生成的结果总是C+uniqueId, 所以应该不会有问题了。

好了,至此所有问题思路解决完毕,贴一下完善FragmentPagerAdapter的完整代码:

/**

  • 加载显示Fragment的ViewPagerAdapter基类

  • 提供可以刷新的方法

  • @author Fly

  • @e-mail 1285760616@qq.com

  • @time 2018/3/22

*/

public class BaseFragmentPagerAdapter extends FragmentPagerAdapter {

private List mFragmentList;

private FragmentManager mFragmentManager;

/*下面两个值用来保存Fragment的位置信息,用以判断该位置是否需要更新/

private SparseArray mFragmentPositionMap;

private SparseArray mFragmentPositionMapAfterUpdate;

public BaseFragmentPagerAdapter(FragmentManager fm, List fragments) {

super(fm);

mFragmentList = fragments;

mFragmentManager = fm;

mFragmentList = fragments;

mFragmentPositionMap = new SparseArray<>();

mFragmentPositionMapAfterUpdate = new SparseArray<>();

setFragmentPositionMap();

setFragmentPositionMapForUpdate();

}

/**

  • 保存更新之前的位置信息,用<hashCode, position>的键值对结构来保存

*/

private void setFragmentPositionMap() {

mFragmentPositionMap.clear();

for (int i = 0; i < mFragmentList.size(); i++) {

mFragmentPositionMap.put(Long.valueOf(getItemId(i)).intValue(), String.valueOf(i));

}

}

/**

  • 保存更新之后的位置信息,用<hashCode, position>的键值对结构来保存

*/

private void setFragmentPositionMapForUpdate() {

mFragmentPositionMapAfterUpdate.clear();

for (int i = 0; i < mFragmentList.size(); i++) {

mFragmentPositionMapAfterUpdate.put(Long.valueOf(getItemId(i)).intValue(), String.valueOf(i));

}

}

/**

  • 在此方法中找到需要更新的位置返回POSITION_NONE,否则返回POSITION_UNCHANGED即可

*/

@Override

public int getItemPosition(Object object) {

int hashCode = object.hashCode();

//查找object在更新后的列表中的位置

String position = mFragmentPositionMapAfterUpdate.get(hashCode);

//更新后的列表中不存在该object的位置了

if (position == null) {

return POSITION_NONE;

} else {

//如果更新后的列表中存在该object的位置, 查找该object之前的位置并判断位置是否发生了变化

int size = mFragmentPositionMap.size();

for (int i = 0; i < size ; i++) {

int key = mFragmentPositionMap.keyAt(i);

if (key == hashCode) {

String index = mFragmentPositionMap.get(key);

if (position.equals(index)) {

//位置没变依然返回POSITION_UNCHANGED

return POSITION_UNCHANGED;

} else {

//位置变了

return POSITION_NONE;

}

}

}

}

return POSITION_UNCHANGED;

}

/**

  • 将指定的Fragment替换/更新为新的Fragment

  • @param oldFragment 旧Fragment

  • @param newFragment 新Fragment

*/

public void replaceFragment(BaseFragment oldFragment, BaseFragment newFragment) {

int position = mFragmentList.indexOf(oldFragment);

if (position == -1) {

return;

}

//从Transaction移除旧的Fragment

removeFragmentInternal(oldFragment);

//替换List中对应的Fragment

mFragmentList.set(position, newFragment);

//刷新Adapter

notifyItemChanged();

}

/**

  • 将指定位置的Fragment替换/更新为新的Fragment,同{@link #replaceFragment(BaseFragment oldFragment, BaseFragment newFragment)}

  • @param position 旧Fragment的位置

  • @param newFragment 新Fragment

*/

public void replaceFragment(int position, BaseFragment newFragment) {

BaseFragment oldFragment = mFragmentList.get(position);

removeFragmentInternal(oldFragment);

mFragmentList.set(position, newFragment);

notifyItemChanged();

}

/**

  • 移除指定的Fragment

  • @param fragment 目标Fragment

*/

public void removeFragment(BaseFragment fragment) {

//先从List中移除

mFragmentList.remove(fragment);

//然后从Transaction移除

removeFragmentInternal(fragment);

//最后刷新Adapter

notifyItemChanged();

}

/**

  • 移除指定位置的Fragment,同 {@link #removeFragment(BaseFragment fragment)}

  • @param position

*/

public void removeFragment(int position) {

BaseFragment fragment = mFragmentList.get(position);

//然后从List中移除

mFragmentList.remove(fragment);

//先从Transaction移除

removeFragmentInternal(fragment);

//最后刷新Adapter

notifyItemChanged();

}

/**

  • 添加Fragment

  • @param fragment 目标Fragment

*/

public void addFragment(BaseFragment fragment) {

mFragmentList.add(fragment);

notifyItemChanged();

}

/**

  • 在指定位置插入一个Fragment

  • @param position 插入位置

  • @param fragment 目标Fragment

*/

public void insertFragment(int position, BaseFragment fragment) {

mFragmentList.add(position, fragment);

notifyItemChanged();

}

private void notifyItemChanged() {

//刷新之前重新收集位置信息

setFragmentPositionMapForUpdate();

notifyDataSetChanged();

setFragmentPositionMap();

}

/**

  • 从Transaction移除Fragment

  • @param fragment 目标Fragment

*/

private void removeFragmentInternal(BaseFragment fragment) {

FragmentTransaction transaction = mFragmentManager.beginTransaction();

transaction.remove(fragment);

transaction.commitNow();

}

/**

  • 此方法不用position做返回值即可破解fragment tag异常的错误

*/

@Override

public long getItemId(int position) {

// 获取当前数据的hashCode,其实这里不用hashCode用自定义的可以关联当前Item对象的唯一值也可以,只要不是直接返回position

return mFragmentList.get(position).hashCode();

}

@Override

public Fragment getItem(int position) {

return mFragmentList.get(position);

}

@Override

public int getCount() {

return mFragmentList.size();

}

public List getFragments() {

return mFragmentList;

}

}

好了,现在这个类可以用来实现Fragment列表中的Fragment替换、删除、添加等操作了,并且可以实时刷新adapter, 你可以试验一下。

尾声

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

  • 思维脑图
  • 性能优化学习笔记


  • 性能优化视频

    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

[外链图片转存中…(img-ym6EAWHk-1714341008237)]
[外链图片转存中…(img-qdtQrDqP-1714341008237)]

[外链图片转存中…(img-GaID09Q2-1714341008237)]
[外链图片转存中…(img-BBWH5uKR-1714341008238)]

  • 性能优化视频
    [外链图片转存中…(img-x9lMVyaD-1714341008238)]
    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值