直入主题:
第一:FragmentPagerAdapter.notifyDataSetChanged无法刷新
首先,我们发现,FragmentPagerAdapter并没有notifyDataSetChanged方法,是其父类PagerAdapter的方法。
找到PagerAdapter源码
/**
* This method should be called by the application if the data backing this adapter has changed
* and associated views should update.
*/
public void notifyDataSetChanged() {
synchronized (this) {
if (mViewPagerObserver != null) {
mViewPagerObserver.onChanged();
}
}
mObservable.notifyChanged();
}
下面的mObservable.notifyChanged()不是我们要追踪的目标,他是用来发起其他的数据更新。我们的目标是mViewPagerObserver.onChanged();
找到mViewPagerObserver被赋值的地方,void setViewPagerObserver(DataSetObserver observer),按ctrl+alt+f7两次,在库文件中查找到在ViewPager中对它进行了赋值
...
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.setViewPagerObserver(mObserver);
....
赋值进去的是PagerObserver,子类赋值给了父容器。
找到重写的PagerObserver类,重写了父类两个方法
private class PagerObserver extends DataSetObserver {
PagerObserver() {
}
@Override
public void onChanged() {
dataSetChanged();
}
@Override
public void onInvalidated() {
dataSetChanged();
}
}
到这里可以发现,最开始我们找的目标mViewPagerObserver.onChanged()调用的其实是PagerObserver里面的onChanged(),即调用了dataSetChanged();
此时心里一堆mmp。
好吧,我们看看dataSetChanged()
void dataSetChanged() {
...
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
mAdapter.startUpdate(this);
isUpdating = true;
}
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
if (mCurItem == ii.position) {
// Keep the current item in the valid range
newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
needPopulate = true;
}
continue;
}
if (ii.position != newPos) {
if (ii.position == mCurItem) {
// Our current item changed position. Follow it.
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
...
}
可以看到,如果getItemPosition返回得到了POSITION_UNCHANGED,则会跳过当前item,得到POSITION_NONE时则会对item进行重载
注意,这是在ViewPager这个类里面的,不管用什么adapter,要更新viewpager都要使getItemPosition返回POSITION_NONE。
所以可以重写getItemPosition,使其返回POSITION_NONE,调用notifyDataSetChanged的时候,设置标志位,让其返回POSITION_NONE。也有人简单粗暴,直接
public int getItemPosition(Object object) {
return POSITION_NONE;
}
也未尝不可,毕竟,现在是手机性能过剩时代。然而为什么我们还是觉得卡卡卡?
第二:还是原来的数据?
完成了上面的步骤后,满怀欣喜,结果一试,看到了界面跳动了一下,然而还是原来的数据。排查了许久,不得其因,最后百度找到了原因。
我们创建FragmentPagerAdapter的时候,必须传一个FragmentManager进来,原因就在这里。每一个item改变的时候会调用instantiateItem得到item实例,我们看看FragmentPagerAdapter里面的instantiateItem
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
函数中判断一下要生成的 Fragment 是否已经生成过了,如果生成过了,就使用旧的,旧的将被 Fragment.attach();如果没有,就调用 getItem() 生成一个新的,新的对象将被 FragmentTransation.add()。
FragmentPagerAdapter 会将所有生成的 Fragment 对象通过 FragmentManager 保存起来备用,以后需要该 Fragment 时,都会从 FragmentManager 读取,而不会再次调用 getItem() 方法。
所以,数据不会被更新。
有些小伙伴说,这是android系统的一个bug,但是,其实android提供另外一个FragmentStatePagerAdapter,这个不会把fragment保存起来,每次都需要都是重新去加载,也就是说,更加耗费资源。这就要看你的需求了
当然,还有高手提出解决方案
1、在继承的fragmentpageadapter类里面添加这么一个方法
public void setFragments(ArrayList<Fragment> fragments) {
if(this.fragments != null){
FragmentTransaction ft = fm.beginTransaction();
for(Fragment f:this.fragments){
ft.remove(f);
}
ft.commit();
ft=null;
fm.executePendingTransactions();
}
this.fragments = fragments;
notifyDataSetChanged();
}
但是已经创建的fragment不会重新创建,所以数据没有变化。
2、重写instantiateItem,把需要修改的fragment缓存去掉,重新加入
@Override
public Object instantiateItem(ViewGroup container, int position) {
//得到缓存的fragment
Fragment fragment = (Fragment) super.instantiateItem(container, position);
//得到tag,这点很重要
String fragmentTag = fragment.getTag();
// LogUtil.e("getItem " + position + mFragments.get(position).getTag());
if(bNotifyDataSetChanged) {
FragmentTransaction ft = mFragmentManager.beginTransaction();
//移除原来的fragment
ft.remove(fragment);
//新的fragment
fragment = mFragments.get(position);
//添加新fragment时必须用前面获得的tag
ft.add(container.getId(), fragment,fragmentTag);
ft.attach(fragment);
ft.commitAllowingStateLoss();
}
return fragment;
}
使用这个方法,在new ViewPagerAdapter的时候,必须保证fragment的list里面已经有fragment,否则无法正常加载。因为如果原来的fragment未空,执行完super.instantiateItem(container, position);已经加载一次了,而没有commit,然后又执行if(bNotifyDataSetChanged)里面的代码,导致重复加载。稍作改变为:
@Override
public Object instantiateItem(ViewGroup container, int position) {
long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
Object object = super.instantiateItem(container, position);
if(fragment == null){
return object;
}
//得到缓存的fragment
fragment = (Fragment) object;
//得到tag,这点很重要
String fragmentTag = fragment.getTag();
Log.e( "instantiateItem",mFragments.get(position).getTag());
//在这里设置判断条件,判断当前fragment是否需要更新,我的需求是全部更新,所以我就不做一一判断了
if(bNotifyDataSetChanged) {
FragmentTransaction ft = mFragmentManager.beginTransaction();
//移除原来的fragment
ft.remove(fragment);
//新的fragment
fragment = mFragments.get(position);
//添加新fragment时必须用前面获得的tag
ft.add(container.getId(), fragment,fragmentTag);
ft.attach(fragment);
ft.commitAllowingStateLoss();
}
return fragment;
}
但是,这么写一点都不严谨,会导致重复加载fragment,不设置viewPager.setOffscreenPageLimit(2)让所有界面一次加载完的话,被销毁的页面重复加载,没有发挥FragmentPagerAdapter的优势,还导致重新加载的页面未空白,实在是下下策,如果需要更新fragment的话,还是按照官方的方法,使用FragmentStatePagerAdapter吧,简单、快捷、方便,出BUG的可能性还小
public class ViewPagerAdapter extends FragmentStatePagerAdapter{
private ArrayList<Fragment> mFragments;
public ViewPagerAdapter(FragmentManager fm,ArrayList<Fragment> fragments) {
super(fm);
mFragments = fragments;
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
@Override
public int getCount() {
return mFragments.size();
}
}
参考资料:
为什么调用 FragmentPagerAdapter.notifyDataSetChanged() 并不能更新其 Fragment?
http://www.cnblogs.com/dancefire/archive/2013/01/02/why-notifydatasetchanged-does-not-work.html
FragmentPagerAdapter刷新fragment最完美解决方案
http://blog.youkuaiyun.com/z13759561330/article/details/40737381