PagerAdapter分析與Fragment懶加載的幾種實(shí)現(xiàn)
Deprecated
時(shí)間:2019年8月23日
新版的ViewPager
的實(shí)現(xiàn)和之前不同,FragmentTransaction
增加setMaxLifecycle(Fragment, Lifecycle.State)
相關(guān)API
,更好使用。下面實(shí)現(xiàn)方法個(gè)人不再推薦使用。
前言
相信使用過ViewPager
的人都知道它的常規(guī)使用方法,當(dāng)然用得最多的仍然是FragmentPagerAdapter
和FragmentStatePagerFragment
,但是會(huì)用不一定用得好,從剛開始開發(fā)APP到現(xiàn)在,我也用過無數(shù)遍,但是最近為了優(yōu)化界面,查看源碼才發(fā)現(xiàn)之前自己“不會(huì)用”。
翻譯
/**
* Base class providing the adapter to populate pages inside of
* a {@link ViewPager}. You will most likely want to use a more
* specific implementation of this, such as
* {@link android.support.v4.app.FragmentPagerAdapter} or
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
*
* <p>When you implement a PagerAdapter, you must override the following methods
* at minimum:</p>
* <ul>
* <li>{@link #instantiateItem(ViewGroup, int)}</li>
* <li>{@link #destroyItem(ViewGroup, int, Object)}</li>
* <li>{@link #getCount()}</li>
* <li>{@link #isViewFromObject(View, Object)}</li>
* </ul>
*
* <p>PagerAdapter is more general than the adapters used for
* {@link android.widget.AdapterView AdapterViews}. Instead of providing a
* View recycling mechanism directly ViewPager uses callbacks to indicate the
* steps taken during an update. A PagerAdapter may implement a form of View
* recycling if desired or use a more sophisticated method of managing page
* Views such as Fragment transactions where each page is represented by its
* own Fragment.</p>
*
* <p>ViewPager associates each page with a key Object instead of working with
* Views directly. This key is used to track and uniquely identify a given page
* independent of its position in the adapter. A call to the PagerAdapter method
* {@link #startUpdate(ViewGroup)} indicates that the contents of the ViewPager
* are about to change. One or more calls to {@link #instantiateItem(ViewGroup, int)}
* and/or {@link #destroyItem(ViewGroup, int, Object)} will follow, and the end
* of an update will be signaled by a call to {@link #finishUpdate(ViewGroup)}.
* By the time {@link #finishUpdate(ViewGroup) finishUpdate} returns the views
* associated with the key objects returned by
* {@link #instantiateItem(ViewGroup, int) instantiateItem} should be added to
* the parent ViewGroup passed to these methods and the views associated with
* the keys passed to {@link #destroyItem(ViewGroup, int, Object) destroyItem}
* should be removed. The method {@link #isViewFromObject(View, Object)} identifies
* whether a page View is associated with a given key object.</p>
*
* <p>A very simple PagerAdapter may choose to use the page Views themselves
* as key objects, returning them from {@link #instantiateItem(ViewGroup, int)}
* after creation and adding them to the parent ViewGroup. A matching
* {@link #destroyItem(ViewGroup, int, Object)} implementation would remove the
* View from the parent ViewGroup and {@link #isViewFromObject(View, Object)}
* could be implemented as <code>return view == object;</code>.</p>
*
* <p>PagerAdapter supports data set changes. Data set changes must occur on the
* main thread and must end with a call to {@link #notifyDataSetChanged()} similar
* to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data
* set change may involve pages being added, removed, or changing position. The
* ViewPager will keep the current page active provided the adapter implements
* the method {@link #getItemPosition(Object)}.</p>
*/
翻譯:
在ViewPager
填充頁面需要提供的adapter的基類。大多數(shù)時(shí)候會(huì)使用這個(gè)類的特定實(shí)現(xiàn),如FragmentPagerAdapter
或FragmentStatePagerAdapter
.
當(dāng)你實(shí)現(xiàn)一個(gè)PagerAdapter
時(shí),你一定至少得重寫以下方法:
instantiateItem(ViewGroup, int)
destroyItem(ViewGroup, int, Object)
getCount()
isViewFromObject(View, Object)
相對(duì)使用AdapterView
的adapter, 使用PagerAdapter
更為簡單。ViewPager
使用回調(diào)來指定操作的步驟,而不是直接使用循環(huán)回收機(jī)制。如果期望實(shí)現(xiàn)回收View
,那么通過PagerAdapter
也是可以實(shí)現(xiàn)的,或者使用更加復(fù)雜的方法來組織每一頁的View
, 例如Fragment
事務(wù)那樣,使用一個(gè)Fragment
來管理View
.
ViewPager使用一個(gè)鍵對(duì)象來關(guān)聯(lián)每一頁,而不是管理View
。這個(gè)鍵用于追蹤和唯一標(biāo)識(shí)在adapter中獨(dú)立位置中的一頁。調(diào)用方法startUpdate(ViewGroup)
表明ViewPager
中的內(nèi)容需要更改。
通過調(diào)用一次或多次調(diào)用instantiateItem(ViewGroup, int)
來構(gòu)造頁面視圖。
調(diào)用destroyItem(ViewGroup, int, Object)
來取消ViewPager
關(guān)聯(lián)的頁面視圖。
最后,當(dāng)一次更新(添加和/或移除)完成之后將會(huì)調(diào)用finishUpdate(ViewGroup)
來通知adapter, 提交關(guān)聯(lián)和/或取消關(guān)聯(lián)的操作。這三個(gè)方法就是用于ViewPager
使用回調(diào)的方式來通知PagerAdapter
來管理其中的頁面。
一個(gè)非常簡單的方式就是使用每頁視圖作為key來關(guān)聯(lián)它們自己,在方法instantiateItem(ViewGroup, int)
中創(chuàng)建和添加它們到ViewGroup
之后,返回該頁視圖。與之相匹配的方法destroyItem(ViewGroup, int, Object)
實(shí)現(xiàn)從ViewGroup
中移除視圖。當(dāng)然必須在isViewFromObject(View, Object)
中這樣實(shí)現(xiàn):return view == object;
.
PagerAdapter支持?jǐn)?shù)據(jù)改變時(shí)刷新界面,數(shù)據(jù)改變必須在主線程中調(diào)用,并在數(shù)據(jù)改變完成后調(diào)用方法notifyDataSetChanged()
, 和AdapterView
中派生自BaseAdapter
相似。一次數(shù)據(jù)的改變可能關(guān)聯(lián)著頁面的添加、移除、或改變位置。ViewPager
將根據(jù)adapter中實(shí)現(xiàn)getItemPosition(Object)
方法返回的結(jié)果,來判斷是否保留當(dāng)前已經(jīng)構(gòu)造的活動(dòng)頁面(即重用,而不完全自行構(gòu)造)。
原理詳解
ViewPager+PagerAdapter的合作關(guān)系:ViewPager
來控制一頁界面構(gòu)造和銷毀的時(shí)機(jī),使用回調(diào)來通知PagerAdapter
具體做什么,PagerAdapter
只需要按照相應(yīng)的步驟做。當(dāng)然為了使用得更好、提供更多的功能,又建議了使用View
的回收工作和管理工作,同時(shí)提供當(dāng)數(shù)據(jù)改變時(shí)的界面刷新工作。
instantiateItem(ViewGroup, int): 構(gòu)造指定位置的頁面。adapter負(fù)責(zé)在這個(gè)方法中添加view到容器中,即使是在finishUpdate(ViewGroup)
才保證完成的。在FragmentPagerAdapter
和FragmentStatePagerAdapter
中,都是返回一個(gè)構(gòu)造的Fragment
.
destroyItem(ViewGroup, populate, Object): 移除指定位置的頁面。adapter負(fù)責(zé)從容器中移除view, 即是最后實(shí)在finishUpdate(ViewGroup)
保證完成的。在FragmentPagerAdapter
和FragmentStatePagerAdapter
中,分別使用FragmentTransition.detach(Fragment)
和FragmentTransition.remove(Fragment)
來邏輯上銷毀Fragment
.
finishUpdate(ViewGroup): 當(dāng)頁面的顯示變化完成式調(diào)用。在這里,你一定保證所有的頁面從容器中合理的添加或移除掉。
setPrimaryItem(ViewGroup, int, Object): 被ViewPager
調(diào)用來通知adapter此時(shí)那個(gè)item應(yīng)該被認(rèn)為是主要的頁面,這個(gè)頁面將在當(dāng)前頁面展示給用戶。正是因?yàn)檫@個(gè)方法,才有在ViewPager
中實(shí)現(xiàn)Fragment
懶加載的機(jī)制。
isViewFromObject(View, Object): 指定當(dāng)前頁面View
是否和指定的key對(duì)象相關(guān)聯(lián)(這個(gè)key對(duì)象是在instantiateItem(ViewGroup, int)
方法返回的)。這個(gè)方法需要PagerAdapter
恰當(dāng)?shù)膶?shí)現(xiàn)。即只要匹配好鍵值對(duì)即可。FragmentPagerAdapter
和FragmentStatePagerAdapter
的實(shí)現(xiàn): return ((Fragment)object).getView() == view;
.
雖然簡單或很少使用到的一些方法不想細(xì)究,不過還是一次性分析完為好,如getPageTitle(int)
, getPageWidth(int)
, getItemPosition(Object)
等。
getPageTitle(int): 返回每頁的標(biāo)題,多用于關(guān)聯(lián)indicator
getPageWidth(int): 返回指定的頁面相對(duì)于ViewPager
寬度的比例,范圍(0.f-1.f]。默認(rèn)值為1.f, 即占滿整個(gè)屏幕。如果是0.5f, 那么在初始狀態(tài)下,默認(rèn)會(huì)出現(xiàn)前兩個(gè)頁面,而primary
主頁面是在ViewPager
的起始位置(通常是屏幕左側(cè)),直到最后一個(gè)頁面在屏幕右側(cè),如果總共5個(gè)頁面,返回值為0.2f, 那么將一次性出現(xiàn)所有的頁面.
getItemPosition(Object): 用于數(shù)據(jù)刷新時(shí)的頁面處理方式。返回值包括三類:POSITION_UNCHANGED
表示位置沒有變化,即在添加或移除一頁或多頁之后該位置的頁面保持不變,可以用于一個(gè)ViewPager
中最后幾頁的添加或移除時(shí),保持前幾頁仍然不變;POSITION_NONE
,表示當(dāng)前頁不再作為ViewPager
的一頁數(shù)據(jù),將被銷毀,可以用于無視View
緩存的刷新;根據(jù)傳過來的參數(shù)Object
來判斷這個(gè)key所指定的新的位置,如總共有5個(gè)頁面,我們想交換第0個(gè)和第4個(gè)的頁面,那么在返回時(shí),可以參考下面的代碼。
// ...
private List<Object> mItems = new ArrayList<>();
{
for (int i = 0; i < 5; i++) {
mItems.add(null);
}
}
@Override
public int getItemPosition(Object object) {
int position = mItems.indexOf(object);
if (position == 0) {
return 4;
} else if (position == 4) {
return 0;
} else {
// 下面兩種都可以
// return POSITION_UNCHANGED;
return position;
}
}
// ...
saveState(): 保存頁面狀態(tài)。用得不多,見到的也是在FragmentStatePagerAdapter
中使用,有不同于FragmentPagerAdapter
的對(duì)于Fragment
的管理機(jī)制。當(dāng)然我們也可以使用這個(gè)來優(yōu)化我們自己的PagerAdapter
, 如主頁Banner.
restoreState(Parcelable, ClassLoader): 和saveState()
搭配使用,用于恢復(fù)頁面狀態(tài)。
FragmentPagerAdapter
可以從很多文章中看到類似這樣的一句話:超出范圍的Fragment
會(huì)被銷毀。所以之前,我一直認(rèn)為的是,不過這是錯(cuò)誤的。下面是翻譯。FragmentPagerAdapter
中通常最多會(huì)保留3個(gè)Fragment
, 超出左右兩側(cè)的Fragment
將被銷毀,滑動(dòng)到時(shí)又會(huì)被重新構(gòu)造。
/**
* Implementation of {@link PagerAdapter} that
* represents each page as a {@link Fragment} that is persistently
* kept in the fragment manager as long as the user can return to the page.
*
* <p>This version of the pager is best for use when there are a handful of
* typically more static fragments to be paged through, such as a set of tabs.
* The fragment of each page the user visits will be kept in memory, though its
* view hierarchy may be destroyed when not visible. This can result in using
* a significant amount of memory since fragment instances can hold on to an
* arbitrary amount of state. For larger sets of pages, consider
* {@link FragmentStatePagerAdapter}.
*
* ...
*/
翻譯:
PagerAdapter的實(shí)現(xiàn)類,使用將一直保留在FragmentManager
中的Fragment
來代表每一頁,直到用戶返回上一頁。
當(dāng)用于典型地使用多靜態(tài)化的Fragment
時(shí),FragmentPagerAdapter
無疑是最好使用的,例如一組tabs. 每個(gè)用戶訪問過的頁面的Fragment
都將會(huì)保留在內(nèi)存中,即使它的視圖層在不可見時(shí)已經(jīng)被銷毀。這可能導(dǎo)致使用比較大數(shù)量的內(nèi)存,因?yàn)?code>Fragment實(shí)例持有任意數(shù)量的狀態(tài)。如果使用大數(shù)據(jù)的頁面,考慮使用FragmentStatePagerAdapter
.
從上面可以看出,即使是超出可視范圍和緩存范圍之外的Fragment
,它的視圖將會(huì)被銷毀,但是它的實(shí)例將會(huì)保留在內(nèi)存中,所以每一頁的Fragment
至始至終都只需要構(gòu)造一次而已。通常是在主頁中使用FragmentPagerAdapter
, 但是超出范圍的Fragment
的視圖會(huì)被銷毀,我們也可以在Fragment
中緩存View
來避免狀態(tài)的丟失,也可以使用另外的機(jī)制,如緩存View
的狀態(tài)。
@Override
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;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment)object).getView());
mCurTransaction.detach((Fragment)object);
}
從上面源碼可以看出,當(dāng)被銷毀時(shí),Fragment
并沒有從FragmentTransition
中移除,而是調(diào)用了FragmentTransition.detach(Fragment)
方法,這樣銷毀了Fragment
的視圖,但是沒有移除Fragment
本身。
FragmentStatePagerAdapter
/**
* Implementation of {@link PagerAdapter} that
* uses a {@link Fragment} to manage each page. This class also handles
* saving and restoring of fragment's state.
*
* <p>This version of the pager is more useful when there are a large number
* of pages, working more like a list view. When pages are not visible to
* the user, their entire fragment may be destroyed, only keeping the saved
* state of that fragment. This allows the pager to hold on to much less
* memory associated with each visited page as compared to
* {@link FragmentPagerAdapter} at the cost of potentially more overhead when
* switching between pages.
*
* ...
*/
PagerAdapter的實(shí)現(xiàn)類,使用Fragment
來管理每一頁。這個(gè)類也會(huì)管理保存和恢復(fù)Fragment
的狀態(tài)。
當(dāng)使用一個(gè)大數(shù)量頁面時(shí),FragmentStatePagerAdapter
將更加有用,工作機(jī)制類似于ListView
. 當(dāng)每頁不再可見時(shí),整個(gè)Fragment
將會(huì)被銷毀,只保留Fragment
的狀態(tài)。相對(duì)于FragmentPagerAdapter
, 這個(gè)將允許頁面持有更少的內(nèi)存。
@Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
}
從源碼可以看出,當(dāng)銷毀Fragment
時(shí),緩存了Fragment
的狀態(tài),并移除了Fragment
的引用。而在構(gòu)造時(shí),顯示判斷是否已經(jīng)在構(gòu)造,如果是則直接返回該Fragment
, 如果不是,則重新構(gòu)造一個(gè)新的Fragment
, 并且如果已經(jīng)緩存了狀態(tài),則將改狀態(tài)傳入Fragment
用于恢復(fù)狀態(tài)。
Fragment懶加載的原理
概念:當(dāng)需要時(shí)才加載,加載之后一直保持該對(duì)象。
而關(guān)于Fragment
實(shí)現(xiàn)的PagerAdapter
都沒有完全保存其引用和狀態(tài)。FragmentPageAdapter
需要重建視圖,FragmentStatePageAdapter
使用狀態(tài)恢復(fù),View
都被銷毀,但是恢復(fù)的方式不同,而通常我們想得到的結(jié)果是,Fragment
一旦被加載,其視圖也不會(huì)被銷毀,即不會(huì)再重新走一遍生命周期。而且ViewPager
為了實(shí)現(xiàn)滑動(dòng)效果,都是預(yù)加載左右兩側(cè)的頁面。
我們通常想要實(shí)現(xiàn)的兩種效果:不提供滑動(dòng),需要時(shí)才構(gòu)造,并且只走一遍生命周期,避免在Fragment
中做過多的狀態(tài)保存和恢復(fù),參考斗魚、QQ、全名,這也是大多數(shù)APP的實(shí)現(xiàn)方式;提供滑動(dòng),但是如果沒有真正顯示在界面上,那么使用站位頁面代替真正的數(shù)據(jù)頁面,參考微信,使用這種實(shí)現(xiàn)的較少。
是否提供滑動(dòng)
提供滑動(dòng),當(dāng)然使用
ViewPager
是最為方便的,但是會(huì)預(yù)加載新的頁面,所以不可避免得,會(huì)提前加載數(shù)據(jù),或是使用站位頁面的方式來實(shí)現(xiàn)懶加載。不提供滑動(dòng),一種方式是使用
ViewPager
, 但是限制其不可滑動(dòng),同時(shí)在選擇指定的頁面時(shí),直接跳轉(zhuǎn),而不是翻頁的方式;另外的實(shí)現(xiàn)方式則是完全拋棄ViewPager
,自己使用FragmentManager
來管理Fragment
, 這種方式的缺點(diǎn)是,離開了ViewPager
,那么與之對(duì)應(yīng)的indicator庫也可能不能使用,所以會(huì)要求在這方面多做一些工作。
ViewPager中實(shí)現(xiàn)懶加載的原理
懶加載需要處理的幾個(gè)問題
- 預(yù)加載
雖然沒有顯示在界面上,但是當(dāng)前頁面的上一頁和下一頁的Fragment
已經(jīng)執(zhí)行了一個(gè)Fragment
能夠顯示在界面上的所有生命周期方法,但是我們想在跳轉(zhuǎn)到該頁時(shí)才真正構(gòu)造數(shù)據(jù)視圖和請(qǐng)求數(shù)據(jù)。那么我們可以使用一個(gè)占位視圖,那么可以想到使用ViewStub
,當(dāng)真正跳轉(zhuǎn)到該頁時(shí),執(zhí)行ViewStub.inflate()
方法,加載真正的數(shù)據(jù)視圖和請(qǐng)求數(shù)據(jù)。
- 視圖保存
當(dāng)某一頁超出可視范圍和預(yù)加載范圍,那么它將會(huì)被銷毀,FragmentStatePagerAdapter
銷毀整個(gè)Fragment
, 我們可以自己保存該Fragment
, 或使用FragmentPagerAdapter
讓FragmentTransition
來保留Fragment
的引用。雖然這樣,但是它的周期方法已經(jīng)走完,那么我們只能手動(dòng)的保存Fragment
根View
的引用,當(dāng)再次重新進(jìn)入新的聲明周期方法時(shí),返回原來的View
- 是否已經(jīng)被用戶所看到
其實(shí)本身而言,FragmentManager
并沒有提供為Fragment
被用戶所看到的回調(diào)方法,而是在FragmentPagerAdapter
和FragmentStatePagerAdapter
中,調(diào)用了Fragment.setUserVisibleHint(boolean)
來表明Fragment
是否已經(jīng)被作為primaryFragment
. 所以這個(gè)方法可以被認(rèn)為是一個(gè)回調(diào)方法。
懶加載實(shí)現(xiàn)
通常想到的,我們是在Fragment
中來根據(jù)生命周期來控制視圖的緩存和數(shù)據(jù)的加載來實(shí)現(xiàn)懶加載的效果,但是我們也可以自定義PagerAdapter
來實(shí)現(xiàn)懶加載。
- 重寫
Fragment
實(shí)現(xiàn)懶加載
/**
* Created by Mycroft on 2017/1/9.
*/
public abstract class LazyFragment extends Fragment {
// Fragment的根View
private View mRootView;
// 檢測聲明周期中,是否已經(jīng)構(gòu)建視圖
private boolean mViewCreated = false;
// 占位圖
private ViewStubCompat mViewStub;
@Nullable
@Override
public final View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if (mRootView != null) {
mViewCreated = true;
return mRootView;
}
final Context context = inflater.getContext();
FrameLayout root = new FrameLayout(context);
mViewStub = new ViewStubCompat(context, null);
mViewStub.setLayoutResource(getResId());
root.addView(mViewStub, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
root.setLayoutParams(new ViewGroup.MarginLayoutParams(ViewGroup.MarginLayoutParams.MATCH_PARENT, ViewGroup.MarginLayoutParams.MATCH_PARENT));
mRootView = root;
mViewCreated = true;
if (mUserVisible) {
realLoad();
}
return mRootView;
}
private boolean mUserVisible = false;
@Override
public final void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mUserVisible = isVisibleToUser;
if (mUserVisible && mViewCreated) {
realLoad();
}
}
// 判斷是否已經(jīng)加載
private boolean mLoaded = false;
/**
* 控制只允許加載一次
*/
private void realLoad() {
if (mLoaded) {
return;
}
mLoaded = true;
onRealViewLoaded(mViewStub.inflate());
}
@Override
public void onDestroyView() {
mViewCreated = false;
super.onDestroyView();
}
/**
* 獲取真正的數(shù)據(jù)視圖
*
* @return
*/
protected abstract int getResId();
/**
* 當(dāng)視圖真正加載時(shí)調(diào)用
*/
protected abstract void onRealViewLoaded(View view);
}
示例可以參考FragmentApp
- 重寫
PagerAdapter
重寫PagerAdapter
解決的是Fragment
生命周期所帶來的視圖保存的問題。
/**
* 和{@link android.support.v4.app.FragmentPagerAdapter}唯一的不同是
* 使用{@link FragmentTransaction#add(int, Fragment, String)}和{@link FragmentTransaction#remove(Fragment)}
* 來代替{@link FragmentTransaction#attach(Fragment)}和{@link FragmentTransaction#detach(Fragment)}
* <p>
* 這樣{@link Fragment}只會(huì)走一遍生命周期
* <p>
* Created by Mycroft on 2017/1/9.
*/
public abstract class LazyFragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "LazyFragmentPagerAdapter";
private static final boolean DEBUG = true;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;
public LazyFragmentPagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
@Override
public void startUpdate(ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this
+ " requires a view id");
}
}
@Override
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.show(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;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment) object).getView());
mCurTransaction.hide((Fragment) object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment) object).getView() == view;
}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
/**
* Return a unique identifier for the item at the given position.
* <p>
* <p>The default implementation returns the given position.
* Subclasses should override this method if the positions of items can change.</p>
*
* @param position Position within this adapter
* @return Unique identifier for the item at position
*/
public long getItemId(int position) {
return position;
}
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
}
而同時(shí),仍然需要重寫Fragment
來進(jìn)行預(yù)加載
/**
* Created by Mycroft on 2017/1/9.
*/
public abstract class BaseLazyFragment extends Fragment {
// 檢測聲明周期中,是否已經(jīng)構(gòu)建視圖
private boolean mViewCreated = false;
// 占位圖
private ViewStubCompat mViewStub;
@Nullable
@Override
public final View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
final Context context = inflater.getContext();
FrameLayout root = new FrameLayout(context);
mViewStub = new ViewStubCompat(context, null);
mViewStub.setLayoutResource(getResId());
root.addView(mViewStub, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
root.setLayoutParams(new ViewGroup.MarginLayoutParams(ViewGroup.MarginLayoutParams.MATCH_PARENT, ViewGroup.MarginLayoutParams.MATCH_PARENT));
mViewCreated = true;
if (mUserVisible) {
realLoad();
}
return root;
}
private boolean mUserVisible = false;
@Override
public final void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mUserVisible = isVisibleToUser;
if (mUserVisible && mViewCreated) {
realLoad();
}
}
// 判斷是否已經(jīng)加載
private boolean mLoaded = false;
/**
* 控制只允許加載一次
*/
private void realLoad() {
if (mLoaded) {
return;
}
mLoaded = true;
onRealViewLoaded(mViewStub.inflate());
}
@Override
public void onDestroyView() {
mViewCreated = false;
super.onDestroyView();
}
/**
* 獲取真正的數(shù)據(jù)視圖
*
* @return
*/
protected abstract int getResId();
/**
* 當(dāng)視圖真正加載時(shí)調(diào)用
*/
protected abstract void onRealViewLoaded(View view);
}
和LazyFragment
唯一的不同是不用自己來保留根View
.
示例可以參考FragmentApp
不使用ViewPager,實(shí)現(xiàn)懶加載的原理
不使用ViewPager
就避免了預(yù)加載的問題,同時(shí)也不會(huì)有Fragment
是否用戶可見的問題,因?yàn)橹挥屑虞d時(shí),用戶才可見。
關(guān)于這一點(diǎn),已經(jīng)有了開源項(xiàng)目,可以參考FragmentNavigator
使用示例可以參考FragmentApp
參考
如何高效的使用ViewPager,以及FragmentPagerAdapter與FragmentStatePagerAdapter的區(qū)別