FragmentPagerAdapter刷新fragment记两点

本文探讨了FragmentPagerAdapter的两个关键问题:1. 解释了为何`notifyDataSetChanged`无法直接刷新Fragment,并提供了重写`getItemPosition`的解决方法。2. 揭示了即使刷新后仍显示原有数据的原因,是因为Fragment被FragmentManager缓存。提出了重写`instantiateItem`的解决方案,但存在潜在问题。建议在需要频繁更新Fragment内容时使用FragmentStatePagerAdapter。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

直入主题:

第一: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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值