PagerAdapter分析與Fragment懶加載的幾種實現(xiàn)
Deprecated
時間:2019年8月23日
新版的ViewPager
的實現(xiàn)和之前不同伍玖,FragmentTransaction
增加setMaxLifecycle(Fragment, Lifecycle.State)
相關(guān)API
,更好使用。下面實現(xiàn)方法個人不再推薦使用。
前言
相信使用過ViewPager
的人都知道它的常規(guī)使用方法,當(dāng)然用得最多的仍然是FragmentPagerAdapter
和FragmentStatePagerFragment
,但是會用不一定用得好,從剛開始開發(fā)APP到現(xiàn)在蛾派,我也用過無數(shù)遍,但是最近為了優(yōu)化界面个少,查看源碼才發(fā)現(xiàn)之前自己“不會用”洪乍。
翻譯
/**
* 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ù)時候會使用這個類的特定實現(xiàn)夜焦,如FragmentPagerAdapter
或FragmentStatePagerAdapter
.
當(dāng)你實現(xiàn)一個PagerAdapter
時壳澳,你一定至少得重寫以下方法:
instantiateItem(ViewGroup, int)
destroyItem(ViewGroup, int, Object)
getCount()
isViewFromObject(View, Object)
相對使用AdapterView
的adapter, 使用PagerAdapter
更為簡單。ViewPager
使用回調(diào)來指定操作的步驟茫经,而不是直接使用循環(huán)回收機(jī)制巷波。如果期望實現(xiàn)回收View
萎津,那么通過PagerAdapter
也是可以實現(xiàn)的,或者使用更加復(fù)雜的方法來組織每一頁的View
, 例如Fragment
事務(wù)那樣抹镊,使用一個Fragment
來管理View
.
ViewPager使用一個鍵對象來關(guān)聯(lián)每一頁锉屈,而不是管理View
。這個鍵用于追蹤和唯一標(biāo)識在adapter中獨立位置中的一頁垮耳。調(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)一次更新(添加和/或移除)完成之后將會調(diào)用finishUpdate(ViewGroup)
來通知adapter, 提交關(guān)聯(lián)和/或取消關(guān)聯(lián)的操作。這三個方法就是用于ViewPager
使用回調(diào)的方式來通知PagerAdapter
來管理其中的頁面铃彰。
一個非常簡單的方式就是使用每頁視圖作為key來關(guān)聯(lián)它們自己绍豁,在方法instantiateItem(ViewGroup, int)
中創(chuàng)建和添加它們到ViewGroup
之后,返回該頁視圖豌研。與之相匹配的方法destroyItem(ViewGroup, int, Object)
實現(xiàn)從ViewGroup
中移除視圖妹田。當(dāng)然必須在isViewFromObject(View, Object)
中這樣實現(xiàn):return view == object;
.
PagerAdapter支持?jǐn)?shù)據(jù)改變時刷新界面唬党,數(shù)據(jù)改變必須在主線程中調(diào)用鹃共,并在數(shù)據(jù)改變完成后調(diào)用方法notifyDataSetChanged()
, 和AdapterView
中派生自BaseAdapter
相似。一次數(shù)據(jù)的改變可能關(guān)聯(lián)著頁面的添加驶拱、移除霜浴、或改變位置。ViewPager
將根據(jù)adapter中實現(xiàn)getItemPosition(Object)
方法返回的結(jié)果蓝纲,來判斷是否保留當(dāng)前已經(jīng)構(gòu)造的活動頁面(即重用阴孟,而不完全自行構(gòu)造)。
原理詳解
ViewPager+PagerAdapter的合作關(guān)系:ViewPager
來控制一頁界面構(gòu)造和銷毀的時機(jī)税迷,使用回調(diào)來通知PagerAdapter
具體做什么永丝,PagerAdapter
只需要按照相應(yīng)的步驟做。當(dāng)然為了使用得更好箭养、提供更多的功能慕嚷,又建議了使用View
的回收工作和管理工作,同時提供當(dāng)數(shù)據(jù)改變時的界面刷新工作毕泌。
instantiateItem(ViewGroup, int): 構(gòu)造指定位置的頁面喝检。adapter負(fù)責(zé)在這個方法中添加view到容器中,即使是在finishUpdate(ViewGroup)
才保證完成的撼泛。在FragmentPagerAdapter
和FragmentStatePagerAdapter
中挠说,都是返回一個構(gòu)造的Fragment
.
destroyItem(ViewGroup, populate, Object): 移除指定位置的頁面。adapter負(fù)責(zé)從容器中移除view, 即是最后實在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此時那個item應(yīng)該被認(rèn)為是主要的頁面外永,這個頁面將在當(dāng)前頁面展示給用戶。正是因為這個方法拧咳,才有在ViewPager
中實現(xiàn)Fragment
懶加載的機(jī)制伯顶。
isViewFromObject(View, Object): 指定當(dāng)前頁面View
是否和指定的key對象相關(guān)聯(lián)(這個key對象是在instantiateItem(ViewGroup, int)
方法返回的)。這個方法需要PagerAdapter
恰當(dāng)?shù)膶崿F(xiàn)骆膝。即只要匹配好鍵值對即可祭衩。FragmentPagerAdapter
和FragmentStatePagerAdapter
的實現(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): 返回指定的頁面相對于ViewPager
寬度的比例政钟,范圍(0.f-1.f]路克。默認(rèn)值為1.f, 即占滿整個屏幕。如果是0.5f, 那么在初始狀態(tài)下养交,默認(rèn)會出現(xiàn)前兩個頁面精算,而primary
主頁面是在ViewPager
的起始位置(通常是屏幕左側(cè)),直到最后一個頁面在屏幕右側(cè)碎连,如果總共5個頁面灰羽,返回值為0.2f, 那么將一次性出現(xiàn)所有的頁面.
getItemPosition(Object): 用于數(shù)據(jù)刷新時的頁面處理方式。返回值包括三類:POSITION_UNCHANGED
表示位置沒有變化鱼辙,即在添加或移除一頁或多頁之后該位置的頁面保持不變廉嚼,可以用于一個ViewPager
中最后幾頁的添加或移除時,保持前幾頁仍然不變倒戏;POSITION_NONE
怠噪,表示當(dāng)前頁不再作為ViewPager
的一頁數(shù)據(jù),將被銷毀杜跷,可以用于無視View
緩存的刷新傍念;根據(jù)傳過來的參數(shù)Object
來判斷這個key所指定的新的位置,如總共有5個頁面葱椭,我們想交換第0個和第4個的頁面捂寿,那么在返回時,可以參考下面的代碼孵运。
// ...
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
的對于Fragment
的管理機(jī)制驳概。當(dāng)然我們也可以使用這個來優(yōu)化我們自己的PagerAdapter
, 如主頁Banner.
restoreState(Parcelable, ClassLoader): 和saveState()
搭配使用赤嚼,用于恢復(fù)頁面狀態(tài)。
FragmentPagerAdapter
可以從很多文章中看到類似這樣的一句話:超出范圍的Fragment
會被銷毀顺又。所以之前更卒,我一直認(rèn)為的是,不過這是錯誤的。下面是翻譯果录。FragmentPagerAdapter
中通常最多會保留3個Fragment
, 超出左右兩側(cè)的Fragment
將被銷毀稚照,滑動到時又會被重新構(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的實現(xiàn)類上枕,使用將一直保留在FragmentManager
中的Fragment
來代表每一頁,直到用戶返回上一頁弱恒。
當(dāng)用于典型地使用多靜態(tài)化的Fragment
時辨萍,FragmentPagerAdapter
無疑是最好使用的,例如一組tabs. 每個用戶訪問過的頁面的Fragment
都將會保留在內(nèi)存中返弹,即使它的視圖層在不可見時已經(jīng)被銷毀锈玉。這可能導(dǎo)致使用比較大數(shù)量的內(nèi)存,因為Fragment
實例持有任意數(shù)量的狀態(tài)义起。如果使用大數(shù)據(jù)的頁面拉背,考慮使用FragmentStatePagerAdapter
.
從上面可以看出,即使是超出可視范圍和緩存范圍之外的Fragment
并扇,它的視圖將會被銷毀去团,但是它的實例將會保留在內(nèi)存中抡诞,所以每一頁的Fragment
至始至終都只需要構(gòu)造一次而已穷蛹。通常是在主頁中使用FragmentPagerAdapter
, 但是超出范圍的Fragment
的視圖會被銷毀,我們也可以在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)被銷毀時蛙吏,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的實現(xiàn)類谓着,使用Fragment
來管理每一頁泼诱。這個類也會管理保存和恢復(fù)Fragment
的狀態(tài)。
當(dāng)使用一個大數(shù)量頁面時赊锚,FragmentStatePagerAdapter
將更加有用治筒,工作機(jī)制類似于ListView
. 當(dāng)每頁不再可見時屉栓,整個Fragment
將會被銷毀,只保留Fragment
的狀態(tài)耸袜。相對于FragmentPagerAdapter
, 這個將允許頁面持有更少的內(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
時堤框,緩存了Fragment
的狀態(tài)域滥,并移除了Fragment
的引用。而在構(gòu)造時蜈抓,顯示判斷是否已經(jīng)在構(gòu)造骗绕,如果是則直接返回該Fragment
, 如果不是,則重新構(gòu)造一個新的Fragment
, 并且如果已經(jīng)緩存了狀態(tài)资昧,則將改狀態(tài)傳入Fragment
用于恢復(fù)狀態(tài)酬土。
Fragment懶加載的原理
概念:當(dāng)需要時才加載,加載之后一直保持該對象格带。
而關(guān)于Fragment
實現(xiàn)的PagerAdapter
都沒有完全保存其引用和狀態(tài)撤缴。FragmentPageAdapter
需要重建視圖,FragmentStatePageAdapter
使用狀態(tài)恢復(fù)叽唱,View
都被銷毀屈呕,但是恢復(fù)的方式不同,而通常我們想得到的結(jié)果是棺亭,Fragment
一旦被加載虎眨,其視圖也不會被銷毀,即不會再重新走一遍生命周期镶摘。而且ViewPager
為了實現(xiàn)滑動效果嗽桩,都是預(yù)加載左右兩側(cè)的頁面。
我們通常想要實現(xiàn)的兩種效果:不提供滑動凄敢,需要時才構(gòu)造碌冶,并且只走一遍生命周期,避免在Fragment
中做過多的狀態(tài)保存和恢復(fù)涝缝,參考斗魚扑庞、QQ、全名拒逮,這也是大多數(shù)APP的實現(xiàn)方式罐氨;提供滑動,但是如果沒有真正顯示在界面上滩援,那么使用站位頁面代替真正的數(shù)據(jù)頁面栅隐,參考微信,使用這種實現(xiàn)的較少。
是否提供滑動
提供滑動约啊,當(dāng)然使用
ViewPager
是最為方便的邑遏,但是會預(yù)加載新的頁面,所以不可避免得恰矩,會提前加載數(shù)據(jù)记盒,或是使用站位頁面的方式來實現(xiàn)懶加載。不提供滑動外傅,一種方式是使用
ViewPager
, 但是限制其不可滑動纪吮,同時在選擇指定的頁面時,直接跳轉(zhuǎn)萎胰,而不是翻頁的方式碾盟;另外的實現(xiàn)方式則是完全拋棄ViewPager
,自己使用FragmentManager
來管理Fragment
, 這種方式的缺點是技竟,離開了ViewPager
冰肴,那么與之對應(yīng)的indicator庫也可能不能使用,所以會要求在這方面多做一些工作榔组。
ViewPager中實現(xiàn)懶加載的原理
懶加載需要處理的幾個問題
- 預(yù)加載
雖然沒有顯示在界面上熙尉,但是當(dāng)前頁面的上一頁和下一頁的Fragment
已經(jīng)執(zhí)行了一個Fragment
能夠顯示在界面上的所有生命周期方法,但是我們想在跳轉(zhuǎn)到該頁時才真正構(gòu)造數(shù)據(jù)視圖和請求數(shù)據(jù)搓扯。那么我們可以使用一個占位視圖检痰,那么可以想到使用ViewStub
,當(dāng)真正跳轉(zhuǎn)到該頁時锨推,執(zhí)行ViewStub.inflate()
方法铅歼,加載真正的數(shù)據(jù)視圖和請求數(shù)據(jù)。
- 視圖保存
當(dāng)某一頁超出可視范圍和預(yù)加載范圍换可,那么它將會被銷毀椎椰,FragmentStatePagerAdapter
銷毀整個Fragment
, 我們可以自己保存該Fragment
, 或使用FragmentPagerAdapter
讓FragmentTransition
來保留Fragment
的引用。雖然這樣锦担,但是它的周期方法已經(jīng)走完俭识,那么我們只能手動的保存Fragment
根View
的引用慨削,當(dāng)再次重新進(jìn)入新的聲明周期方法時洞渔,返回原來的View
- 是否已經(jīng)被用戶所看到
其實本身而言,FragmentManager
并沒有提供為Fragment
被用戶所看到的回調(diào)方法缚态,而是在FragmentPagerAdapter
和FragmentStatePagerAdapter
中磁椒,調(diào)用了Fragment.setUserVisibleHint(boolean)
來表明Fragment
是否已經(jīng)被作為primaryFragment
. 所以這個方法可以被認(rèn)為是一個回調(diào)方法。
懶加載實現(xiàn)
通常想到的玫芦,我們是在Fragment
中來根據(jù)生命周期來控制視圖的緩存和數(shù)據(jù)的加載來實現(xiàn)懶加載的效果浆熔,但是我們也可以自定義PagerAdapter
來實現(xiàn)懶加載。
- 重寫
Fragment
實現(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)視圖真正加載時調(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}只會走一遍生命周期
* <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;
}
}
而同時慎皱,仍然需要重寫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)視圖真正加載時調(diào)用
*/
protected abstract void onRealViewLoaded(View view);
}
和LazyFragment
唯一的不同是不用自己來保留根View
.
示例可以參考FragmentApp
不使用ViewPager叶骨,實現(xiàn)懶加載的原理
不使用ViewPager
就避免了預(yù)加載的問題茫多,同時也不會有Fragment
是否用戶可見的問題,因為只有加載時忽刽,用戶才可見天揖。
關(guān)于這一點,已經(jīng)有了開源項目跪帝,可以參考FragmentNavigator
使用示例可以參考FragmentApp
參考
如何高效的使用ViewPager今膊,以及FragmentPagerAdapter與FragmentStatePagerAdapter的區(qū)別