**
参考文章:
**
https://www.jianshu.com/p/266861496508
http://blog.sina.com.cn/u/2017385987
https://blog.csdn.net/z13759561330/article/details/40737381
https://blog.csdn.net/happylishang/article/details/78961984
**
目录
**
一.问题复现
1.从ViewPager的3种适配器说起
这里我们先来盘一下ViewPager的三种适配器
PagerAdapter:
重写:getCount()、isViewFromObject()、instantiateItem()、destroyItem()
对应的item为View;
FragmentPagerAdapter:
重写:getCount()、getItem()、
对应的item为Fragment,适用于Fragment较少的情况;
会缓存所有Fragment,为每个fragment添加Tag,需要的时候根据Tag查找;
首次显示时候add,非首次根据Tag查找然后attach;
移除的时候detach,注意这里并未remove;
FragmentStateAdapter:
重写:getCount()、getItem()
对应item为Fragment,适用于Fragment较多的情况;
只缓存最近显示的Fragment ,不会为fragment添加Tag ,需要的时候如果没有缓存就重新创建;
显示时候如果没有缓存就add,有缓存直接显示
移除的时候remove
2.问题来了,刷新dapter的数据源 Viewpager并不能及时正确地刷新UI页面
操作步骤:
首先,对dapter进行增删改的操作;
然后调用adapter.notifyDateSetChanged()观察ViewPager的页面
亲测结果如下:
PagerAdapter | FragmentPagerAdapter | FragmentStateAdapter | |
---|---|---|---|
更新Item | 生效但不及时 | 不生效 | 生效但不及时 |
增加Item | 生效但不及时 | 不生效 | crash |
删除Item | 页面错乱 | 不生效 | crash |
清空Item | 残留当前页面 | OK | 残留当前页面 |
二. 问题分析
1.知其然——问题很严重
更新生效但不及时: C级bug
更新新无效: C级+bug
页面错乱: B级bug
crash: A级bug
试想一下,如果我们自己的app出现这种bug,是何等的事故哟!但是这种事情就实实在在地发生了,而且还是发生在拥有10亿级用户的Android操作系统的官方api中!
2.知其所以然——Why!
让我们来分析下这个问题的可能原因:
原因一、我们自己写的adapter有问题。
原因二、这是bug。
排除法 搞起
case 原因二: 这个bug能发布出来只有一种情况——Google发布Android的api连冒烟测试都没通过。可能性几乎为0 排除
case 原因一:是我们的代码写得不够规范。可能性较大
那么我们的代码那里写的有问题呢?为什么按Google的api只重写几个PagerAdapter必要的抽象方法,在数据源更新的时候,调用notifyDateSetChanged()不能达到像ListView和RecyclerView那样的效果呢?
这一切都要从PagerAdapter的刷新机制说起。
下图是刷新单个Item的流程图:
调用流程:
- 调用 PagerAdapter 的 notifyDataSetChanged() 方法
- 触发 getItemPosition() 方法,该方法会为每个 Item 返回状态码POSITION_NONE 或POSITION_UNCHANGED
- 如果是 POSITION_NONE,那么该 Item 会被 destroyItem() 方法 remove 掉,然后instantiateItem()重新加载 显示刷新后的页面
- 如果是 POSITION_UNCHANGED,就不会重新加载,默认是 POSITION_UNCHANGED,无刷新效果
那么问题来了,这个getItemPosition()是干什么的,我跟ta 认识,吗?
我们回过头来再来回顾我们写的PagerAdapter都重写了哪些方法
PagerAdapter:
重写:getCount()、isViewFromObject()、instantiateItem()、destroyItem()
FragmentPagerAdapter:
重写:getCount()、getItem()、
FragmentStateAdapter:
重写:getCount()、getItem()
并没有找到这个getItemPosition()啊,别急既然这方法是PagerAdapter的,我们去源码里找下。
public int getItemPosition(@NonNull Object object) {
return PagerAdapter.POSITION_UNCHANGED;//默认返回POSITION_UNCHANGED
}
...
哈哈,“真相只有一个!”就是因为父类PagerAdapter的getItemPosition()直接返回POSITION_UNCHANGED,才导致这一连串的刷新问题。
那么,我们是不是只要在子类中重写该方法返回POSITION_NONE就万事大吉了呢?So,easy?妈妈再也不用担心我的Viewpager不刷新?
老办法,我们试一下子!
PagerAdapter | FragmentPagerAdapter | FragmentStateAdapter | |
---|---|---|---|
更新Item | OK | 不生效 | OK |
增加Item | OK | 不生效 | OK |
删除Item | OK | 不生效 | OK |
清空Item | OK | OK | OK |
三. 解决方案
1.PagerAdapter 重写该方法返回POSITION_NONE 可以解决刷新问题
2.FragmentPagerAdapter 效果并不理想 这跟它内部对Fragment的缓存有关
3.FragmentStateAdapter 重写该方法返回POSITION_NONE 可以解决刷新问题
@Override
public int getItemPosition(@NonNull Object object) {
return POSITION_NONE;//返回POSITION_NONE 强制刷新
}
关于1和3 的【可以解决】这里有几点说明一下:
First…我们这里所谓的刷新数据源包括:增加,删除、更新、清空
Second…这里的刷新虽然能达到效果,但是所有Item都会刷新,不能实现类似RecyclerView只更新单个tem的效果;
Third…如果只是更新数据源 不涉及动态增加、删除、清空操作,那么 可以这样优化:
通过为每个Item设置Tag,然后重建的时候根据Tag决定是否更新
注意 这种Tag方式只能保证更新操作不会出问题
关于2FragmentPagerAdapter的【效果并不理想】
尝试的解决方案是这样的,先讲下思路:
在Adapter内维护一个所有fragment的集合fragments,注意这个集合需要在adapter内通过new创建,不能由外部指定,加载数据需要先clear 再addAll();
每次外部数据源list有刷新操作的时候:
然后通过fragmentManager将所有fragment移除
先将内部维护的fragemnt清空,
接着内部集合fragments 再addAll()
最后调用notifyDataSetChanged()
完整代码如下:
package com.darcy.hellotest.ui.viewpager.adapter;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
public class FragmentsAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;//内维护一个所有fragment的集合fragments
private FragmentManager fragmentManager;
public FragmentsAdapter(FragmentManager fm, List<Fragment> list) {
super(fm);
this.fragmentManager = fm;
fragments = new ArrayList<>();//集合需要在adapter内通过new创建
setFragments(list);
}
/**
* 刷新 通过修改adapter数据源实现
* 每次更新刷新所有fragment
*
* @param fragments 新数据源
*/
public void setFragments(List<Fragment> fragments) {
if (this.fragments != null) {
FragmentTransaction transaction = fragmentManager.beginTransaction();
for (Fragment f : this.fragments) {//通过fragmentManager将所有fragment移除
transaction.remove(f);
}
transaction.commit();
fragmentManager.executePendingTransactions();
}
assert this.fragments != null;
this.fragments.clear();//清空
this.fragments.addAll(fragments);//addAll操作
notifyDataSetChanged();//调用notifyDataSetChanged()
}
@Override
public Fragment getItem(int i) {
return fragments.get(i);
}
@Override
public int getCount() {
return fragments.size();
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
return super.instantiateItem(container, position);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
super.destroyItem(container, position, object);
}
@Override
public int getItemPosition(@NonNull Object object) {
return POSITION_NONE;
}
}
Activity中的刷新调用代码如下:
//真更新 通过修改适配器数据源实现
private void refresh2() {
fragments.set(0, PagerFragment.newInstance("更新测试"));
mPagerAdapter.setFragments(fragments);
}
private void add() {
fragments.add(0, PagerFragment.newInstance("添加Item测试"));
mPagerAdapter.setFragments(fragments);
}
private void delete() {
fragments.remove(0);
mPagerAdapter.setFragments(fragments);
}
private void clean() {
fragments.clear();
mPagerAdapter.setFragments(fragments);
}
这个方法的实验结果如下:
FragmentPagerAdapter | |
---|---|
更新Item | 有效 但是会导致下一个页面首次加载时空白 |
增加Item | 有效 但是会导致下一个页面首次加载时空白 |
删除Item | 有效 但是会导致下一个页面首次加载时空白 |
清空Item | OK |
很显然 目前这个方案还不完善,哪位大佬知道,还望不吝赐教,这里先行谢过了(教我,让我做你的舔狗~~~)
所以我现在的做法是向FragmentStatePagerAdapter靠拢,既然FragmentPagerAdapter效果不理想,那就先拿FragmentStatePagerAdapter救急。
四. 总结
1.以上我们复现了ViewPager的刷新机制存在的问题——不能及时正确地更新页面
2.接着我们分析了这种情况出现的原因——PagerAdapter的getItemPosition()直接返回POSITION_UNCHANGED 导致页面不刷新
3.然后我们提出了解决方案:在子类中重写该方法返回POSITION_NONE
这个方案针对PagerAdapter和FragmentStatePagerAdapter效果理想
对FragmentPagerAdapter由于其内部的缓存机制 效果并不理想
4.最后我们还尝试针对FragmentPagerAdapter的缓存机制进行优化——强制清除缓存,但是效果也有瑕疵。期待大佬出来指点江山~~
此致:
我是漩涡小学生