FragmentStatePagerAdapter保存恢复下拉刷新Fragment内存数据

作者:李暖光 时间:2023-07-17 20:36:40 

一、前言

本篇文章聚焦在“如何使用FragmentStatePagerAdapter来保存Fragment的数据、在内存中加载出保存后的数据再填充到Fragment中、以及在下拉刷新之后刷新Fragment的最新数据”。对于 ViewPage、PagerAdapter、FragmentPagerAdapter、ViewPager2、FragmentStateAdapter 的如何使用及区别并不在本篇文章内讨论。

如果对于新开发的功能,建议使用 ViewPager2 + FragmentStateAdapter 来构建; 对于需要维护的项目,且项目中使用到 ViewPage + FragmentStatePagerAdapter,还是需要学习下 FragmentStatePagerAdapter 的一些用法的。

FragmentStatePagerAdapter,适用于多个Fragment的场景,默认情况下会在内存中保留3个Fragment,当前Fragment、左侧及右侧Frgment,在已经都访问一遍的情况下,其他的Fragment会被销毁掉(即走了onDestory)

FragmentStatePagerAdapter保存、恢复的GIF图

FragmentStatePagerAdapter保存恢复下拉刷新Fragment内存数据

FragmentStatePagerAdapter 遇到下拉刷新时,清空内存缓存数据,重新请求最新的网络数据的GIF图:

FragmentStatePagerAdapter保存恢复下拉刷新Fragment内存数据

二、FragmentStatePagerAdapter保存、恢复及刷新数据的效果

1、FragmentStatePagerAdapter保存、恢复

如第一个GIF图所示,将Fragment的网络数据保存到内存中,然后下次Fragment再次构建时使用内存中数据。

首先有五个Fragment,分别是Fragment0 - Fragment4。

(1)第一步:进入到该首页后,从左到右滑动到最后一个Fragment,加载网络数据并填充到对应的Fragment中。

(2)第二步:从右到左依次滑动到第一个Fragment,此时可以看到 Fragment3 及 Fragment4 展示的是网络数据,而Fragment0 到 Fragment3 展示的内存数据。

这是因为在第一步时,当界面展示最后一个Fragment的时候,这个时候仅有Fragment3 及 Frgment4 是存活的,其他Fragment均被销毁掉了。在销毁Fragment之前干了一件事,将数据保存到内存中去,当销毁掉的Fragment再次被构建时,将之前保存下来的数据填充到新构建的Fragment中展示。

(3)第三步:再次从左到右滑动到最后一个Fragment,此时可以看到所有的Fragment使用的都是内存数据了。道理同上

仅在Fragment中做两件事情即可:

(1)保存Fragment数据到内存中, 重写onSaveInstanceState方法

(2)从内存中恢复Fragment数据,在onCreateView方法中的判断参数savedInstanceState不为null,取其参数中的数据

2、FragmentStatePagerAdapter 遇到下拉刷新时,清空内存缓存数据,重新请求最新的网络数据

如第二个GIF图所示,下拉刷新后,请求最新的网络数据填充到Fragment中

(1)在当前Fragment4页面下,进行下拉刷新

(2)下拉刷新的数据回来后,刷新当前页面,展示最新的网络数据

(3)从右滑动到左时,能够看到所有的Fragment页面均是请求了最新的数据

这里面有几个注意的点:

(1)下拉刷新后,仅在当前页面显示时才会进行该页面的网络请求,而不是将所有Fragment页面都进行网络请求

(2)对应Fragment4而言,因为下拉刷新后,Fragment4 及 Fragment3 是存活状态,因此需要单独地处理它们的刷新(其实这里需要考虑地是当前Fragment及其左右的Fragment)

(3)而对应非Fragment4、Fragment3的Fragment们,因为已经被销毁掉了,所以只需要在下拉刷新后,将它们的内存数据清除掉就可以了,这样下次构建的时候,没有内存数据,则会重新请求网络数据展示的。

关于下拉刷新的可以参考这篇文章Android:对现有布局添加自定义的下拉刷新布局(阻尼滑动、悬停、回弹动画效果)

三、具体实现

1、如何保存Fragment的数据到内存中

FragmentStatePagerAdapter在销毁Fragment的时候,会调用destoryItem方法,从而间接地调用了Fragment的onSaveInstanceState方法来保存数据。

public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
   // ...
   mSavedState.set(position, fragment.isAdded()
           ? mFragmentManager.saveFragmentInstanceState(fragment) : null)
   // ...
}

因此,仅需要在Fragment中重写onSaveInstanceState方法

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
   // 这里可以根据需要保存数据
   outState.putString(TAG, DATA_STR);
   super.onSaveInstanceState(outState);
}

2、如何恢复内存中的数据填充到Fragment中

在构建Fragment的时候,可以充分地利用onCreateView方法中的Bundle类型的savedInstanceState参数:

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
   mContext = getActivity();
   View view = inflater.inflate(R.layout.demo_fragment_layout, null, false);
   initView(view);
   // savedInstanceState不为空的时候,可以从内存中取出数据
   if (savedInstanceState != null) {
       mContentTv.setText(((String) savedInstanceState.get(TAG)) + ", 从内存中取得数据");
       mContentTv.setTextColor(mContext.getResources().getColor(R.color.black));
   } else {
      loadNetData();
   }
   return view;
}

3、如何在下拉刷新后,每个Fragment请求最新的网络数据并刷新展示

下拉刷新,是将所有Fragment的数据都刷新成最新的网络数据,因此在下拉刷新后,要做下面几件事:

(1)想办法刷新当前还活着的Fragment

如第二个GIF图所示,当在Fragment4的时候,下拉刷新,则此时活着的Fragment就只剩下Fragment4及Fragment3了,那么如何刷新Fragment4及Fragment3呢?

1)刷新Fragment4

因为是在Fragment4页面进行下拉刷新的,所以可以在下拉刷新回调事件中调用:

@Override
public void onComplete() {
   Log.d(TAG, "onComplete... ");
   mMyFragmentAdapter.clearSavedState();
   // 当下拉刷新后,分别将当前页面进行刷新、左右页面在页面选择后再进行刷新
   // 刷新当前页面
   int currentPosition = mViewPager.getCurrentItem();
   refreshUI(currentPosition);
   // 记录左边位置,待mViewPage切换的时机进行刷新
   mLeftRefresh = currentPosition - 1 >= 0 ? currentPosition - 1 : -1;
   // 记录右边位置,待mViewPage切换的时机进行刷新
   mRightRefresh = currentPosition + 1 <= mTabItemList.size() ? currentPosition + 1 : -1;
}
// 刷新当前页面
private void refreshUI(int position) {
   Fragment currentFragment = mMyFragmentAdapter.getCurrentFragment(position);
   if (currentFragment instanceof DemoFragment) {
       ((DemoFragment) currentFragment).loadNetData();
   }
}

在回调中,刷新当前页面的时候需要获取当前的Fragment,而Fragment都是由Adapter管理的,所以怎么获取到呢?可以使用反射去取:参考这个getCurrentFragment方法

public class MyFragmentAdapter extends FragmentStatePagerAdapter {
   private static final String TAG = "MyFragmentAdapter";
   public static final String POSITION = "POSITION";
   private List<TabItem> mTabItemList = new ArrayList<>();
   public MyFragmentAdapter(FragmentManager fm) {
       super(fm);
   }
   public void setTabItemList(List<TabItem> tabItemList) {
       mTabItemList = tabItemList;
   }
   @NonNull
   @Override
   public Fragment getItem(int position) {
       Bundle bundle = new Bundle();
       bundle.putInt(POSITION, position);
       Fragment demoFragment = new DemoFragment();
       demoFragment.setArguments(bundle);
       return demoFragment;
   }
   @Override
   public int getCount() {
       return mTabItemList.size();
   }
   @Nullable
   @Override
   public CharSequence getPageTitle(int position) {
       return mTabItemList.get(position).getName();
   }
   public void clearSavedState() {
       try {
           Class clazz = getClass().getSuperclass();
           Field savedStateField = clazz.getDeclaredField("mSavedState");
           savedStateField.setAccessible(true);
           Object object = savedStateField.get(this);
           ArrayList<Fragment.SavedState> savedStates = (ArrayList<Fragment.SavedState>) object;
           savedStates.clear();
       } catch (Exception e) {
           Log.e(TAG, "clear saved state is error");
       }
   }
  // 获取当前的Fragment
   @Nullable
   public Fragment getCurrentFragment(int position) {
       try {
           Class clazz = getClass().getSuperclass();
           Field mFragmentsField = clazz.getDeclaredField("mFragments");
           mFragmentsField.setAccessible(true);
           Object object = mFragmentsField.get(this);
           ArrayList<Fragment> fragmentArrayList = (ArrayList<Fragment>) object;
           return fragmentArrayList.get(position);
       } catch (Exception e) {
           Log.e(TAG, "getCurrentFragment is error");
           return null;
       }
   }
}

2)刷新Fragment3

因为Fragment3此时并没显示,可以做一个position标记,记录下当切换到Fragment3的时候,进行网络请求并刷新。因此,在回调中,看到的是记录左边位置(如果左边位置不存在,则赋值-1)

// 记录左边位置,待mViewPage切换的时机进行刷新
mLeftRefresh = currentPosition - 1 &gt;= 0 ? currentPosition - 1 : -1;

在ViewPager中的页面切换回调中,拿到position和mLeftRefresh对比,如果相同则请求网络数据并刷新:

mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
   @Override
   public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
   }
   @Override
   public void onPageSelected(int position) {
       if (position == mLeftRefresh) {
           refreshUI(position);
           mLeftRefresh = -1;
       } else if (position == mRightRefresh) {
           refreshUI(position);
           mRightRefresh = -1;
       }
   }
   @Override
   public void onPageScrollStateChanged(int state) {
   }
});

(2)想办法刷新已经销毁掉的Fragment

已经销毁掉的Fragment,其内存数据保存在mSavedState列表中

private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<>();

因此,需要做的就是在下拉刷新之后,将这个mSavedState列表数据进行清空即可。

到这个,你可能有思路了,就是反射取FragmentStatePagerAdapter的这个mSaveState私有属性做clear即可

// 下拉刷新的回调
@Override
public void onComplete() {
   Log.d(TAG, "onComplete... ");
   // 清除内存数据
   mMyFragmentAdapter.clearSavedState();
}
// MyFragmentAdapter
public void clearSavedState() {
   try {
       Class clazz = getClass().getSuperclass();
       Field savedStateField = clazz.getDeclaredField("mSavedState");
       savedStateField.setAccessible(true);
       Object object = savedStateField.get(this);
       ArrayList<Fragment.SavedState> savedStates = (ArrayList<Fragment.SavedState>) object;
       savedStates.clear();
   } catch (Exception e) {
       Log.e(TAG, "clear saved state is error");
   }
}

来源:https://juejin.cn/post/7202188243114262586

标签:FragmentStatePagerAdapter,Fragment,保存恢复,下拉刷新
0
投稿

猜你喜欢

  • Java集合之Comparable和Comparator接口详解

    2022-10-04 06:03:44
  • Android中JSON的4种解析方式使用和对比

    2023-05-03 11:32:10
  • 详解Java中如何正确书写单例模式

    2022-06-20 01:35:00
  • RocketMQ消息生产者是如何选择Broker示例详解

    2023-11-10 21:45:49
  • C# double和decimal数据类型以截断的方式保留指定的小数位数

    2021-12-09 02:19:04
  • Android AndFix热修复原理详情

    2023-03-02 09:17:07
  • springboot 使用poi进行数据的导出过程详解

    2022-12-01 07:23:31
  • C# 动态调用WebService的示例

    2023-07-04 05:40:20
  • sqlite查询结果在listview中展示的实现

    2021-07-27 06:29:07
  • Android下拉刷新控件SwipeRefreshLayout源码解析

    2023-04-03 20:42:16
  • 详解备忘录模式及其在Java设计模式编程中的实现

    2023-08-24 22:34:02
  • spring cloud gateway 限流的实现与原理

    2023-04-10 16:47:56
  • Spring内存缓存Caffeine的基本使用教程分享

    2023-05-26 00:30:33
  • C#纹理画刷TextureBrush用法实例

    2023-03-17 07:23:12
  • java @Value(

    2023-10-05 02:54:47
  • Java环境配置与编译运行详解

    2022-10-02 12:42:24
  • Java高级特性之反射机制实例详解

    2023-10-08 06:33:51
  • C# DataTable与Model互转的示例代码

    2022-02-07 19:37:44
  • Java利用递归算法实现查询斐波那契数

    2023-08-04 00:02:29
  • Java线程的五种状态介绍

    2023-06-16 01:22:15
  • asp之家 软件编程 m.aspxhome.com