解决ViewPager展示Fragment时重新设置setAdapter不会重置Fragment的bug

用过ListView和RecycleView的人都知道不管当前列表的浏览记录在哪里,只要重新setAdapter,列表就会重置,即从第一条item开始显示.
因此,想当然的我也就认为ViewPager也是这个样子的.结果并不是我想的那么简单,重复setAdapter并没什么卵用,Fragment的状态还是上一次所看到的样子,并没有重新初始化,Fragment的几什么周期方法一个都没有运行…实在无语…

经调试后发现ViewPager重新setAdapter并没有执行FragmentPagerAdapter的getItem方法,因此Adapter中的Fragment并不会重新初始化.

例如我的FragmentPagerAdapter :

class PageAdapter extends FragmentPagerAdapter {
    public PageAdapter(FragmentManager fm) {
        super(fm);
    }
    @Override
    public Fragment getItem(int position) {
        Category c= mData.get(position % mData.size());
        return NewsFragment.newInstance(c.id);
    }
    @Override
    public int getCount() {
        return mData.size();
    }
}

既然getItem方法没有调用,那么就得看看其父类FragmentPagerAdapter 的源码了,看看getItem方法是在哪里调用的.发现是在父类的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);//从缓存中查找该fragment
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position); //这里才会执行getItem方法
        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 = mFragmentManager.findFragmentByTag(name);这句代码,这里FragmentManager是通过tag别名的方式来从缓存中查找对应的Fragment,其tag的命名是有一套命名规范的,是通过ViewPager的id和getItemId的返回值来生成tag.

读到这里的时候,你应该就可以明白怎么做了,如果想要保证setAdapter方法会执行getItem方法,那么就得保证findFragmentByTag方法返回的Fragment是null的.
如何做到呢?
我这里想到2种解决思路:
1.重写FragmentPagerAdapter 的getItemId方法,每次都返回不同的id,这样就可以保证生成的tag别名每次都是不一样的,因此也无法从缓存中查找到值
2.清空mFragmentManager内的缓存.

清空FragmentManager的缓存

我这里主要介绍的是第2种方法,即清空缓存的方式.
首先mFragmentManager,你得明白这个是什么,这个其实就是你创建FragmentPagerAdapter 的实现类的时候赋值的,一般是通过getSupportFragmentManager或者getChildFragmentManager方式得到的值,前者是在Activity中使用,后者是在Fragment中使用.

public abstract class FragmentPagerAdapter extends PagerAdapter {
    private static final String TAG = "FragmentPagerAdapter";
    private static final boolean DEBUG = false;

    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;
    private Fragment mCurrentPrimaryItem = null;

    public FragmentPagerAdapter(FragmentManager fm) {
        mFragmentManager = fm; //这里赋值
    }
    ....
}

FragmentManager 是一个抽象类,其findFragmentByTag也是抽象的方法

public abstract Fragment findFragmentByTag(String tag);

因此只能找他的实现类了,即FragmentManagerImpl
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {..}
查看其findFragmentByTag的具体实现:

@Override
public Fragment findFragmentByTag(String tag) {
    if (tag != null) {
        // First look through added fragments.
        for (int i=mAdded.size()-1; i>=0; i--) { //1
            Fragment f = mAdded.get(i);
            if (f != null && tag.equals(f.mTag)) {
                return f;
            }
        }
    }
    if (mActive != null && tag != null) { //2
        // Now for any known fragment.
        for (int i=mActive.size()-1; i>=0; i--) {
            Fragment f = mActive.valueAt(i);
            if (f != null && tag.equals(f.mTag)) {
                return f;
            }
        }
    }
    return null;
}

注意查看上面标注位置的1和2,分别有2个属性mAdded和mActive。
findFragmentByTag的操作就是操作这2个东东,那么这2个东东到底是啥呢,mAdded其实是一个ArrayList集合,而mActive就是一个SparseArray,观察源码便知真相在这里插入图片描述
mAdded和mActive都各自维护了已经加载过的Fragment,也就是这里所说的缓存,既然这样,我就可以通过反射拿到这2个东东,然后调用它们的clear方法来清空缓存,那么findFragmentByTag方法拿到的结果就是null了.

下面看具体的逻辑,在setAdapter方法之前执行下面代码:

//先保证ViewPager之前已设置过Adapter,这样才有可能存在缓存
if (mContentVp.getAdapter() != null) {
	//获取FragmentManager实现类的class对象,这里指的就是FragmentManagerImpl
    Class<? extends FragmentManager> aClass = getChildFragmentManager().getClass();
    try {
    	//1.获取其mAdded字段
        Field f = aClass.getDeclaredField("mAdded");
        f.setAccessible(true);
        //强转成ArrayList
        ArrayList<Fragment> list = (ArrayList) f.get(getChildFragmentManager());
        //清空缓存
        list.clear();
        
        //2.获取mActive字段
        f = aClass.getDeclaredField("mActive");
        f.setAccessible(true);
        //强转成SparseArray
        SparseArray<Fragment> array  = (SparseArray) f.get(getChildFragmentManager());
     	//清空缓存
        array.clear();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
//再次设置ViewPager的Adapter
mContentVp.setAdapter(new PageAdapter(getChildFragmentManager()));

ok,至此大功告成.

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
好的,我会为您讲解关于Android中ViewPager和Fragment的使用。 ViewPager和Fragment是Android中非常常用的组件,他们可以一起使用来实现滑动页面效果。ViewPager是一个可以左右滑动切换页面的布局容器,而Fragment作为ViewPager的子页面,可以在ViewPager中进行动态添加和移除。 下面我们将分别介绍ViewPager和Fragment的使用。 ## ViewPager的使用 ### 1.布局文件 在布局文件中,我们需要使用ViewPager作为容器,将需要滑动切换的页面放入其中。如下所示: ``` <androidx.viewpager.widget.ViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent"/> ``` ### 2.创建Adapter 我们需要创建一个Adapter继承自PagerAdapter,并重写以下方法: ``` public class MyPagerAdapter extends PagerAdapter { private List<Fragment> mFragments; public MyPagerAdapter(List<Fragment> fragments) { mFragments = fragments; } @Override public int getCount() { return mFragments.size(); } @Override public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { return view == object; } @NonNull @Override public Object instantiateItem(@NonNull ViewGroup container, int position) { Fragment fragment = mFragments.get(position); container.addView(fragment.getView()); return fragment.getView(); } @Override public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { container.removeView((View) object); } } ``` ### 3.设置Adapter 在Activity或Fragment中,我们需要创建ViewPager的实例,并设置Adapter。如下所示: ``` ViewPager viewPager = findViewById(R.id.viewPager); List<Fragment> fragments = new ArrayList<>(); fragments.add(new Fragment1()); fragments.add(new Fragment2()); fragments.add(new Fragment3()); MyPagerAdapter adapter = new MyPagerAdapter(fragments); viewPager.setAdapter(adapter); ``` 这样,我们就完成了ViewPager的使用。 ## Fragment的使用 ### 1.创建Fragment 我们需要创建一个继承自Fragment的类,并重写以下方法: ``` public class Fragment1 extends Fragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment1, container, false); return view; } } ``` ### 2.布局文件 我们需要在Fragment中添加布局文件,如下所示: ``` <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:text="Fragment1" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> ``` 这样,我们就完成了Fragment的使用。 ## ViewPager和Fragment的结合使用 通过以上介绍,我们已经知道了如何使用ViewPager和Fragment了。现在我们需要将它们结合起来使用。 ### 1.创建Fragment 我们需要创建多个Fragment作为ViewPager的子页面。 ### 2.创建Adapter 我们需要创建一个PagerAdapter,将Fragment添加到ViewPager中。如上所示,我们已经创建了一个MyPagerAdapter。 ### 3.设置Adapter 在Activity或Fragment中,我们需要创建ViewPager的实例,并设置Adapter。如上所示,我们已经使用ViewPager的setAdapter方法设置了MyPagerAdapter。 这样,我们就完成了ViewPager和Fragment的结合使用。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值