FragmentStatePagerAdapter保存恢復(fù)下拉刷新Fragment內(nèi)存數(shù)據(jù)
一、前言
本篇文章聚焦在“如何使用FragmentStatePagerAdapter來(lái)保存Fragment的數(shù)據(jù)、在內(nèi)存中加載出保存后的數(shù)據(jù)再填充到Fragment中、以及在下拉刷新之后刷新Fragment的最新數(shù)據(jù)”。對(duì)于 ViewPage、PagerAdapter、FragmentPagerAdapter、ViewPager2、FragmentStateAdapter 的如何使用及區(qū)別并不在本篇文章內(nèi)討論。
如果對(duì)于新開發(fā)的功能,建議使用 ViewPager2 + FragmentStateAdapter 來(lái)構(gòu)建; 對(duì)于需要維護(hù)的項(xiàng)目,且項(xiàng)目中使用到 ViewPage + FragmentStatePagerAdapter,還是需要學(xué)習(xí)下 FragmentStatePagerAdapter 的一些用法的。
FragmentStatePagerAdapter,適用于多個(gè)Fragment的場(chǎng)景,默認(rèn)情況下會(huì)在內(nèi)存中保留3個(gè)Fragment,當(dāng)前Fragment、左側(cè)及右側(cè)Frgment,在已經(jīng)都訪問(wèn)一遍的情況下,其他的Fragment會(huì)被銷毀掉(即走了onDestory)
FragmentStatePagerAdapter保存、恢復(fù)的GIF圖

FragmentStatePagerAdapter 遇到下拉刷新時(shí),清空內(nèi)存緩存數(shù)據(jù),重新請(qǐng)求最新的網(wǎng)絡(luò)數(shù)據(jù)的GIF圖:

二、FragmentStatePagerAdapter保存、恢復(fù)及刷新數(shù)據(jù)的效果
1、FragmentStatePagerAdapter保存、恢復(fù)
如第一個(gè)GIF圖所示,將Fragment的網(wǎng)絡(luò)數(shù)據(jù)保存到內(nèi)存中,然后下次Fragment再次構(gòu)建時(shí)使用內(nèi)存中數(shù)據(jù)。
首先有五個(gè)Fragment,分別是Fragment0 - Fragment4。
(1)第一步:進(jìn)入到該首頁(yè)后,從左到右滑動(dòng)到最后一個(gè)Fragment,加載網(wǎng)絡(luò)數(shù)據(jù)并填充到對(duì)應(yīng)的Fragment中。
(2)第二步:從右到左依次滑動(dòng)到第一個(gè)Fragment,此時(shí)可以看到 Fragment3 及 Fragment4 展示的是網(wǎng)絡(luò)數(shù)據(jù),而Fragment0 到 Fragment3 展示的內(nèi)存數(shù)據(jù)。
這是因?yàn)樵诘谝徊綍r(shí),當(dāng)界面展示最后一個(gè)Fragment的時(shí)候,這個(gè)時(shí)候僅有Fragment3 及 Frgment4 是存活的,其他Fragment均被銷毀掉了。在銷毀Fragment之前干了一件事,將數(shù)據(jù)保存到內(nèi)存中去,當(dāng)銷毀掉的Fragment再次被構(gòu)建時(shí),將之前保存下來(lái)的數(shù)據(jù)填充到新構(gòu)建的Fragment中展示。
(3)第三步:再次從左到右滑動(dòng)到最后一個(gè)Fragment,此時(shí)可以看到所有的Fragment使用的都是內(nèi)存數(shù)據(jù)了。道理同上
僅在Fragment中做兩件事情即可:
(1)保存Fragment數(shù)據(jù)到內(nèi)存中, 重寫onSaveInstanceState方法
(2)從內(nèi)存中恢復(fù)Fragment數(shù)據(jù),在onCreateView方法中的判斷參數(shù)savedInstanceState不為null,取其參數(shù)中的數(shù)據(jù)
2、FragmentStatePagerAdapter 遇到下拉刷新時(shí),清空內(nèi)存緩存數(shù)據(jù),重新請(qǐng)求最新的網(wǎng)絡(luò)數(shù)據(jù)
如第二個(gè)GIF圖所示,下拉刷新后,請(qǐng)求最新的網(wǎng)絡(luò)數(shù)據(jù)填充到Fragment中
(1)在當(dāng)前Fragment4頁(yè)面下,進(jìn)行下拉刷新
(2)下拉刷新的數(shù)據(jù)回來(lái)后,刷新當(dāng)前頁(yè)面,展示最新的網(wǎng)絡(luò)數(shù)據(jù)
(3)從右滑動(dòng)到左時(shí),能夠看到所有的Fragment頁(yè)面均是請(qǐng)求了最新的數(shù)據(jù)
這里面有幾個(gè)注意的點(diǎn):
(1)下拉刷新后,僅在當(dāng)前頁(yè)面顯示時(shí)才會(huì)進(jìn)行該頁(yè)面的網(wǎng)絡(luò)請(qǐng)求,而不是將所有Fragment頁(yè)面都進(jìn)行網(wǎng)絡(luò)請(qǐng)求
(2)對(duì)應(yīng)Fragment4而言,因?yàn)橄吕⑿潞螅現(xiàn)ragment4 及 Fragment3 是存活狀態(tài),因此需要單獨(dú)地處理它們的刷新(其實(shí)這里需要考慮地是當(dāng)前Fragment及其左右的Fragment)
(3)而對(duì)應(yīng)非Fragment4、Fragment3的Fragment們,因?yàn)橐呀?jīng)被銷毀掉了,所以只需要在下拉刷新后,將它們的內(nèi)存數(shù)據(jù)清除掉就可以了,這樣下次構(gòu)建的時(shí)候,沒(méi)有內(nèi)存數(shù)據(jù),則會(huì)重新請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)展示的。
關(guān)于下拉刷新的可以參考這篇文章Android:對(duì)現(xiàn)有布局添加自定義的下拉刷新布局(阻尼滑動(dòng)、懸停、回彈動(dòng)畫效果)
三、具體實(shí)現(xiàn)
1、如何保存Fragment的數(shù)據(jù)到內(nèi)存中
FragmentStatePagerAdapter在銷毀Fragment的時(shí)候,會(huì)調(diào)用destoryItem方法,從而間接地調(diào)用了Fragment的onSaveInstanceState方法來(lái)保存數(shù)據(jù)。
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) {
// 這里可以根據(jù)需要保存數(shù)據(jù)
outState.putString(TAG, DATA_STR);
super.onSaveInstanceState(outState);
}
2、如何恢復(fù)內(nèi)存中的數(shù)據(jù)填充到Fragment中
在構(gòu)建Fragment的時(shí)候,可以充分地利用onCreateView方法中的Bundle類型的savedInstanceState參數(shù):
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不為空的時(shí)候,可以從內(nèi)存中取出數(shù)據(jù)
if (savedInstanceState != null) {
mContentTv.setText(((String) savedInstanceState.get(TAG)) + ", 從內(nèi)存中取得數(shù)據(jù)");
mContentTv.setTextColor(mContext.getResources().getColor(R.color.black));
} else {
loadNetData();
}
return view;
}
3、如何在下拉刷新后,每個(gè)Fragment請(qǐng)求最新的網(wǎng)絡(luò)數(shù)據(jù)并刷新展示
下拉刷新,是將所有Fragment的數(shù)據(jù)都刷新成最新的網(wǎng)絡(luò)數(shù)據(jù),因此在下拉刷新后,要做下面幾件事:
(1)想辦法刷新當(dāng)前還活著的Fragment
如第二個(gè)GIF圖所示,當(dāng)在Fragment4的時(shí)候,下拉刷新,則此時(shí)活著的Fragment就只剩下Fragment4及Fragment3了,那么如何刷新Fragment4及Fragment3呢?
1)刷新Fragment4
因?yàn)槭窃贔ragment4頁(yè)面進(jìn)行下拉刷新的,所以可以在下拉刷新回調(diào)事件中調(diào)用:
@Override
public void onComplete() {
Log.d(TAG, "onComplete... ");
mMyFragmentAdapter.clearSavedState();
// 當(dāng)下拉刷新后,分別將當(dāng)前頁(yè)面進(jìn)行刷新、左右頁(yè)面在頁(yè)面選擇后再進(jìn)行刷新
// 刷新當(dāng)前頁(yè)面
int currentPosition = mViewPager.getCurrentItem();
refreshUI(currentPosition);
// 記錄左邊位置,待mViewPage切換的時(shí)機(jī)進(jìn)行刷新
mLeftRefresh = currentPosition - 1 >= 0 ? currentPosition - 1 : -1;
// 記錄右邊位置,待mViewPage切換的時(shí)機(jī)進(jìn)行刷新
mRightRefresh = currentPosition + 1 <= mTabItemList.size() ? currentPosition + 1 : -1;
}
// 刷新當(dāng)前頁(yè)面
private void refreshUI(int position) {
Fragment currentFragment = mMyFragmentAdapter.getCurrentFragment(position);
if (currentFragment instanceof DemoFragment) {
((DemoFragment) currentFragment).loadNetData();
}
}
在回調(diào)中,刷新當(dāng)前頁(yè)面的時(shí)候需要獲取當(dāng)前的Fragment,而Fragment都是由Adapter管理的,所以怎么獲取到呢?可以使用反射去?。簠⒖歼@個(gè)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");
}
}
// 獲取當(dāng)前的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
因?yàn)镕ragment3此時(shí)并沒(méi)顯示,可以做一個(gè)position標(biāo)記,記錄下當(dāng)切換到Fragment3的時(shí)候,進(jìn)行網(wǎng)絡(luò)請(qǐng)求并刷新。因此,在回調(diào)中,看到的是記錄左邊位置(如果左邊位置不存在,則賦值-1)
// 記錄左邊位置,待mViewPage切換的時(shí)機(jī)進(jìn)行刷新 mLeftRefresh = currentPosition - 1 >= 0 ? currentPosition - 1 : -1;
在ViewPager中的頁(yè)面切換回調(diào)中,拿到position和mLeftRefresh對(duì)比,如果相同則請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)并刷新:
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)想辦法刷新已經(jīng)銷毀掉的Fragment
已經(jīng)銷毀掉的Fragment,其內(nèi)存數(shù)據(jù)保存在mSavedState列表中
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<>();
因此,需要做的就是在下拉刷新之后,將這個(gè)mSavedState列表數(shù)據(jù)進(jìn)行清空即可。
到這個(gè),你可能有思路了,就是反射取FragmentStatePagerAdapter的這個(gè)mSaveState私有屬性做clear即可
// 下拉刷新的回調(diào)
@Override
public void onComplete() {
Log.d(TAG, "onComplete... ");
// 清除內(nèi)存數(shù)據(jù)
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");
}
}以上就是FragmentStatePagerAdapter保存恢復(fù)下拉刷新Fragment內(nèi)存數(shù)據(jù)的詳細(xì)內(nèi)容,更多關(guān)于FragmentStatePagerAdapter Fragment的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android結(jié)合kotlin使用coroutine的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Android結(jié)合kotlin使用coroutine的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
分享Android平板電腦上開發(fā)應(yīng)用程序不能全屏顯示的問(wèn)題解決
在一個(gè)8寸屏的Android平板電腦上開發(fā)應(yīng)用程序(游戲程序),開始的時(shí)候,總是不能全屏顯示,也不知道怎么設(shè)置才可以2013-05-05
Android中AlarmManager+Notification實(shí)現(xiàn)定時(shí)通知提醒功能
本篇文章主要介紹了Android中AlarmManager+Notification實(shí)現(xiàn)定時(shí)通知提醒功能,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-10-10
Android開發(fā)之HttpClient異步請(qǐng)求數(shù)據(jù)的方法詳解【附demo源碼下載】
這篇文章主要介紹了Android開發(fā)之HttpClient異步請(qǐng)求數(shù)據(jù)的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android HttpClient異步請(qǐng)求數(shù)據(jù)的相關(guān)操作技巧,并附帶完整demo源碼供讀者下載參考,需要的朋友可以參考下2017-11-11
Android 音樂(lè)播放器的開發(fā)實(shí)例詳解
本文主要講解Android 音樂(lè)播放器的開發(fā),這里給大家提供一個(gè)簡(jiǎn)單的示例代碼,和實(shí)現(xiàn)效果圖,有需要開發(fā)音樂(lè)播放器的朋友可以參考下2016-08-08
初識(shí)Android?PowerManagerService省電模式
這篇文章主要介紹了初識(shí)Android?PowerManagerService省電模式,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-08-08
Android 中使用ExpandableListView 實(shí)現(xiàn)分組的實(shí)例
這篇文章主要介紹了Android 中使用ExpandableListView 實(shí)現(xiàn)分組的實(shí)例的相關(guān)資料,這里提供實(shí)例代碼及實(shí)現(xiàn)效果圖,需要的朋友可以參考下2016-12-12
android 線性布局LinearLayout實(shí)例代碼
android 線性布局LinearLayout實(shí)例代碼,需要的朋友可以參考一下2013-05-05

