今天我們來一起實(shí)現(xiàn)“愛閱”首頁(yè)滑動(dòng)切換分類瀏覽閱讀的效果,并將ViewPager和TabLayout結(jié)合起來用以實(shí)現(xiàn)頂部導(dǎo)航欄的分類展示,并增加點(diǎn)擊快速切換分類的功能。ViewPager+TabLayout也是當(dāng)前最炙手可熱的組合方式,我會(huì)首先對(duì)基礎(chǔ)的知識(shí)點(diǎn)做一些講解,然后對(duì)我們今天的內(nèi)容進(jìn)行實(shí)現(xiàn)。
首先看一下效果圖:

點(diǎn)此進(jìn)入目錄:[干貨] 十天 教你從創(chuàng)意到上線APP
1、ViewPager簡(jiǎn)介
ViewPager是android擴(kuò)展包v4包中的類,這個(gè)類可以讓用戶左右切換當(dāng)前的view
- ViewPager類直接繼承了ViewGroup類,所以它是一個(gè)容器類,可以在其中添加其他的view類。
- ViewPager類需要一個(gè)PagerAdapter適配器類給它提供數(shù)據(jù)。
- ViewPager經(jīng)常和Fragment一起使用,并且提供了專門的FragmentPagerAdapter和FragmentStatePagerAdapter類供Fragment中的ViewPager使用。
2、ViewPager的適配器
上文的簡(jiǎn)介中提到了PagerAdapter,其實(shí)和RecyclerView、ListView等控件使用一樣,ViewPager需要設(shè)置PagerAdapter來完成頁(yè)面和數(shù)據(jù)的綁定,這個(gè)PagerAdapter是一個(gè)基類適配器,我們經(jīng)常用它來實(shí)現(xiàn)App引導(dǎo)圖,它的子類有FragmentPagerAdapter和FragmentStatePagerAdapter,這兩個(gè)子類適配器用于和Fragment一起使用,在安卓應(yīng)用中它們就像RecyclerView一樣出現(xiàn)的頻繁。
(1)實(shí)現(xiàn)一個(gè)最基本的PagerAdapter
public class AdapterViewpager extends PagerAdapter {
private List<View> mViewList;
public AdapterViewpager(List<View> mViewList) {
this.mViewList = mViewList;
}
@Override
public int getCount() {//必須實(shí)現(xiàn)
return mViewList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {//必須實(shí)現(xiàn)
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {//必須實(shí)現(xiàn),實(shí)例化
container.addView(mViewList.get(position));
return mViewList.get(position);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {//必須實(shí)現(xiàn),銷毀
container.removeView(mViewList.get(position));
}
}
instantiateItem()
可以看到instantiateItem()做了兩件事,第一:將當(dāng)前視圖添加到container中,第二:返回當(dāng)前View。也就是說instantiateItem()的功能是創(chuàng)建指定位置的頁(yè)面視圖,并且適配器有責(zé)任增加即將創(chuàng)建的View視圖添加到這里給定的container中。它的返回值代表新增視圖頁(yè)面的Object(Key),這里沒必要非要返回視圖本身,也可以返回可以代表當(dāng)前頁(yè)面的任意值,只要你可以與你增加的View一一對(duì)應(yīng)即可,比如position變量也可以做為Key。isViewFromObject()
該函數(shù)用來判斷 instantiateItem() 函數(shù)所返回來的 Key 與一個(gè)頁(yè)面視圖是否是代表的同一個(gè)視圖(即它倆是否是對(duì)應(yīng)的,對(duì)應(yīng)的表示同一個(gè) View),如果對(duì)應(yīng)的是同一個(gè)View回 true,否則返回 false。destroyItem()
該方法的功能是移除一個(gè)給定位置的頁(yè)面,適配器有責(zé)任從容器中刪除這個(gè)視圖,這是為了確保在 finishUpdate(viewGroup) 返回時(shí)視圖能夠被移除。
不過說道destroyItem()這個(gè)方法的時(shí)候就不得不提及ViewPager的刷新問題了,因?yàn)閂iewPager的刷新并不是我們最初想的調(diào)用一下notifyDataSetChanged()就完事了這么簡(jiǎn)單的,我們會(huì)在后文說明“愛閱”中遇到的坑和解決辦法。
(2)實(shí)現(xiàn)一個(gè)最基本的FragmentPagerAdapter
/**
* Created by : WGH.
*/
public class ViewPagerAdapter extends FragmentPagerAdapter {
private ArrayList<Category> mCategoryList;
private Context mContext;
private Fragment mFragment;
public ViewPagerAdapter(FragmentManager fm, Context context) {
super(fm);
mContext = context;
mCategoryList = DataCacheHelper.getInstance().getPagerChildCategories();
}
public void onCategorysChange(String key) {
if (key != null) {
mCategoryList = DataCache.getInstance().getChildCategorys(key);
notifyDataSetChanged();
} else {
DLog.e("onCategorysChange() Error!");
}
}
@Override
public Fragment getItem(int position) {// 必須實(shí)現(xiàn)
return ViewPagerFragment.newInstance(mContext, mCategoryList.get(position));
}
@Override
public int getCount() {// 必須實(shí)現(xiàn)
if (mCategoryList != null) {
return mCategoryList.size();
} else {
return 0;
}
}
@Override
public CharSequence getPageTitle(int position) {// 選擇性實(shí)現(xiàn)
return mCategoryList.get(position).getName();
}
public String getFragmentTag(int viewPagerId, int fragmentPosition) {
return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
}
public String getFragmentTag(int viewPagerId, int fragmentPosition) {
return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
FragmentStatePagerAdapter的實(shí)現(xiàn)和FragmentPagerAdapter的實(shí)現(xiàn)一樣就不在寫了。而這里的getItemPosition()之所以這樣寫同樣和ViewPager的刷新有關(guān),我們后文講解。
三個(gè)適配器的區(qū)別:
PagerAdapter是基類適配器是一個(gè)通用的ViewPager適配器,相比PagerAdapter,F(xiàn)ragmentPagerAdapter和FragmentStatePagerAdapter更專注于每一頁(yè)是Fragment的情況,而這兩個(gè)子類適配器使用情況也是有區(qū)別的。FragmentPagerAdapter適用于頁(yè)面比較少的情況,F(xiàn)ragmentStatePagerAdapter適用于頁(yè)面比較多的情況,我們可以從兩個(gè)適配器的源碼得知。
- FragmentStatePagerAdapter
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);// fragment被釋放后這里得到的null值
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);// fragment被釋放后或者是初次進(jìn)入頁(yè)面拿到新的Fragment實(shí)例
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);// 新的Fragment實(shí)例 是add上去的
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);// 真正釋放了fragment實(shí)例
mCurTransaction.remove(fragment);
}
- FragmentPagerAdapter
@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);// 因?yàn)閒ragment實(shí)例沒有被真正釋放,所以可以直接attach效率高。
} else {
fragment = getItem(position);// 初始化頁(yè)面的時(shí)候拿到fragment的實(shí)例
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);// 并沒有真正釋放fragment對(duì)象只是detach
}
從源碼中我們可以看出:FragmentStatePagerAdapter中fragment實(shí)例在destroyItem的時(shí)候被真正釋放,F(xiàn)ragmentPagerAdapter中的fragment實(shí)例在destroyItem的時(shí)候并沒有真正釋放fragment對(duì)象只是detach。所以FragmentStatePagerAdapter省內(nèi)存,而FragmentPagerAdapter會(huì)消耗更多的內(nèi)存,帶來的好處就是效率更高一些。所以得出這樣的結(jié)論:FragmentPagerAdapter適用于頁(yè)面比較少的情況,F(xiàn)ragmentStatePagerAdapter適用于頁(yè)面比較多的情況,但是對(duì)性能有要求的情況下,推薦使用FragmentPagerAdapter。因此,“愛閱”為了突出性能,選擇了FragmentPagerAdapter進(jìn)行業(yè)務(wù)功能實(shí)現(xiàn)。
4、ViewPager的翻頁(yè)動(dòng)畫
為ViewPager設(shè)置適配器后,就可以正常使用了,接下來我們?yōu)閂iewPager增加翻頁(yè)動(dòng)畫,ViewPager提供了PageTransformer接口用于實(shí)現(xiàn)翻頁(yè)動(dòng)畫。
官方提供的PageTransformer實(shí)現(xiàn)例子
public class DepthPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.75f;
public void transformPage(View view, float position) {
Log.d("DepthPageTransformer", view.getTag() + " , " + position + "");
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else if (position <= 1) { // (0,1]
// Fade the page out.
view.setAlpha(1 - position);
// Counteract the default slide transition
view.setTranslationX(pageWidth * -position);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;
@SuppressLint("NewApi")
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
Log.e("TAG", view + " , " + position + "");
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1)
{ // [-1,1]
// Modify the default slide transition to shrink the page as well
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
// Scale the page down (between MIN_SCALE and 1)
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
// Fade the page relative to its size.
view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE)
/ (1 - MIN_SCALE) * (1 - MIN_ALPHA));
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
實(shí)現(xiàn)翻頁(yè)動(dòng)畫的關(guān)鍵就是重寫transformPage方法,方法里有兩個(gè)參數(shù)view和position,理解這兩個(gè)參數(shù)非常重要。假設(shè)有三個(gè)頁(yè)面view1、view2、view3從左至右在viewPager中顯示:往左滑動(dòng)時(shí)view1、view2、view3的position都是不斷變小的;往右滑動(dòng)時(shí)view1、view2、view3的position都是不斷變大的。當(dāng)position是正負(fù)無窮大時(shí)view就離開屏幕視野了。因此最核心的控制邏輯是在[-1,0]和(0,1]這兩個(gè)區(qū)間,通過設(shè)置透明度、平移、旋轉(zhuǎn)、縮放等動(dòng)畫組合可以實(shí)現(xiàn)各式各樣的頁(yè)面變化效果。
5、ViewPager的高級(jí)用法
這里主要介紹的是ViewPager結(jié)合第三方庫(kù)實(shí)現(xiàn)小圓點(diǎn)指示器效果和ViewPager結(jié)合design庫(kù)實(shí)現(xiàn)tab切換,首先我們看下實(shí)現(xiàn)效果:


代碼并不很復(fù)雜就不過多啰嗦了,大家可以到這里來獲取源碼:ViewPager指示器效果
6、ViewPager使用中遇到的坑
在“愛閱”的開發(fā)過程中我遇到了這樣的現(xiàn)象:更新數(shù)據(jù)源之后視圖并沒有立即刷新,多滑動(dòng)幾次再次回到更新的Item時(shí)才更新。
對(duì)應(yīng)刷新部分的代碼編寫是這樣的:
public void onCategorysChange(String key) {
if (key != null) {
mCategoryList = DataCache.getInstance().getChildCategorys(key);
notifyDataSetChanged();
} else {
DLog.e("onCategorysChange() Error!");
}
}
并且不僅僅是更新數(shù)據(jù),在單純的添加和刪除數(shù)據(jù)的時(shí)候同樣會(huì)出現(xiàn)這樣的問題。那么究竟是什么原因呢?我們對(duì)上文提到的三種適配器分別做出方案解答。
(1)PagerAdapter的解決方案
先來了解下 ViewPager 的刷新過程:
-
刷新的起始
ViewPager 的刷新是從調(diào)用其 PagerAdapter 的 notifyDataSetChanged() 方法開始的,那先看看該方法的源碼:
public void notifyDataSetChanged() {
synchronized (this) {
if (mViewPagerObserver != null) {
mViewPagerObserver.onChanged();
}
}
mObservable.notifyChanged();
}
-
DataSetObservable 的 notifyChanged()
上面的方法中出現(xiàn)了兩個(gè)關(guān)鍵的成員變量:
private final DataSetObservable mObservable = new DataSetObservable();
private DataSetObserver mViewPagerObserver;
發(fā)現(xiàn)這是我們熟知的觀察者模式,接下來看看 mObservable.notifyChanged() 做了些什么工作:
public void notifyChanged() {
synchronized(mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
notifyChanged() 方法中是很典型的觀察者模式中遍歷所有的 Observer,通知變化發(fā)生了的代碼,接下來看看這個(gè) mObservers 包含哪些 Observer 。
-
DataSetObserver
直接從 mObservers 點(diǎn)進(jìn)去你會(huì)發(fā)現(xiàn)這個(gè):
protected final ArrayList<T> mObservers = new ArrayList<T>();
public abstract class DataSetObserver {
public void onChanged() {
// Do nothing
}
public void onInvalidated() {
// Do nothing
}
}
-
PagerObserver 內(nèi)部類
PagerObserver 是 ViewPager 中的一個(gè)內(nèi)部類,實(shí)現(xiàn)就是調(diào)用了 ViewPager 中的 dataSetChanged() 方法,真正的關(guān)鍵來了:
private class PagerObserver extends DataSetObserver {
@Override
public void onChanged() {
dataSetChanged();
}
@Override
public void onInvalidated() {
dataSetChanged();
}
}
-
ViewPager 的 dataSetChanged()
這個(gè)方法的實(shí)現(xiàn)較長(zhǎng),里面的邏輯看上去挺復(fù)雜的,這里就不展示全部的源碼了,列下關(guān)鍵點(diǎn):
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
...
continue;
}
}
上面截取的代碼中 for 循環(huán)里面有兩個(gè) continue 語(yǔ)句,這可能是比較關(guān)鍵的代碼,幸好不用我們繼續(xù)深入了,官方給出了解釋:如果 Item 的位置如果沒有發(fā)生變化,則返回 POSITION_UNCHANGED。如果返回了 POSITION_NONE,表示該位置的 Item 已經(jīng)不存在了。默認(rèn)的實(shí)現(xiàn)是假設(shè) Item 的位置永遠(yuǎn)不會(huì)發(fā)生變化,而返回 POSITION_UNCHANGED。
說道這里,我們需要了解下 Viewpager 的刷新過程:
在每次調(diào)用 PagerAdapter 的 notifyDataSetChanged() 方法時(shí),都會(huì)激活 getItemPosition(Object object) 方法,該方法會(huì)遍歷 ViewPager 的所有 Item(由緩存的 Item 數(shù)量決定,默認(rèn)為當(dāng)前頁(yè)和其左右加起來共3頁(yè),這個(gè)可以自行設(shè)定,但是至少會(huì)緩存2頁(yè)),為每個(gè) Item 返回一個(gè)狀態(tài)值(POSITION_NONE/POSITION_UNCHANGED),如果是 POSITION_NONE,那么該 Item 會(huì)被 destroyItem(ViewGroup container, int position, Object object) 方法 remove 掉然后重新加載,如果是 POSITION_UNCHANGED就不會(huì)重新加載。默認(rèn)是 POSITION_UNCHANGED,所以如果不重寫 getItemPosition(Object object)并修改返回值,就無法看到 notifyDataSetChanged() 的刷新效果。
最簡(jiǎn)單的解決方案:
那就是直接重寫PagerAdapter的getItemPosition(Object object)方法,將返回值固定為POSITION_NONE。正如前文所寫的那樣:
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
該方案的缺點(diǎn):
有個(gè)很明顯的缺陷,那就是會(huì)刷新所有的 Item,這將導(dǎo)致系統(tǒng)資源的浪費(fèi),所以這種方式不適合數(shù)據(jù)量較大的場(chǎng)景。
注意:
這種方式還有一個(gè)需要注意的地方,就是重寫 destoryItem() 方法:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
// 把 Object 強(qiáng)轉(zhuǎn)為 View,然后將 view 從 ViewGroup 中清除
container.removeView((View) object);
}
最簡(jiǎn)方案的優(yōu)化
在 instantiateItem() 方法中給每個(gè) View 添加 tag(使用 setTag() 方法),然后在 getItemPosition() 方法中通過 View.getTag() 來判斷是否是需要刷新的頁(yè)面,是就返回 POSITION_NONE,否就返回 POSITION_UNCHANGED。
注意:
這里有一點(diǎn)要注意的是,當(dāng)清空數(shù)據(jù)源的時(shí)候需要返回 POSITION_NONE,可用如下代碼:
if (mDataList != null && mDataList.size()==0) {
return POSITION_NONE;
}
關(guān)于 PagerAdapter 的介紹就到這里了,雖然 FragmentPagerAdapter 與 FragmentStatePagerAdapter 都是繼承自 PagerAdapter。但是這兩個(gè)是專門為以 Fragment 為 Item 的 ViewPager 所準(zhǔn)備的,所以有其特殊性,我們下面來介紹。
(2)FragmentPagerAdapter的解決方案
上面通過使 getItemPosition() 方法返回 POSITION_NONE 到達(dá)數(shù)據(jù)源變化(也就是調(diào)用 notifyDataSetChanged())時(shí)刷新視圖的目的。但是當(dāng)我們使用 Fragment 作為 ViewPager 的 Item 時(shí),就需要多考慮一些了,而且一般是使用 FragmentPagerAdapter 或者 FragmentStatePagerAdapter。
解決方案:清除 FragmentManager 中緩存的 Fragment
當(dāng)數(shù)據(jù)源發(fā)生變化時(shí),先將 FragmentManger 里面所有緩存的 Fragment 全部清除,然后重新創(chuàng)建,這樣達(dá)到刷新視圖的目的。下面給出核心代碼:
public class FPagerAdapter1 extends FragmentPagerAdapter {
private ArrayList<Fragment> mFragmentList;
private FragmentManager mFragmentManager;
public FPagerAdapter1(FragmentManager fm, List<Integer> types) {
super(fm);
this.mFragmentManager = fm;
mFragmentList = new ArrayList<>();
for (int i = 0, size = types.size(); i < size; i++) {
mFragmentList.add(FragmentTest.instance(i));
}
setFragments(mFragmentList);
}
public void updateData(List<Integer> dataList) {
ArrayList<Fragment> fragments = new ArrayList<>();
for (int i = 0, size = dataList.size(); i < size; i++) {
Log.e("FPagerAdapter1", dataList.get(i).toString());
fragments.add(FragmentTest.instance(dataList.get(i)));
}
setFragments(fragments);
}
private void setFragments(ArrayList<Fragment> mFragmentList) {
if(this.mFragmentList != null){
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
for(Fragment f:this.mFragmentList){
fragmentTransaction.remove(f);
}
fragmentTransaction.commit();
mFragmentManager.executePendingTransactions();
}
this.mFragmentList = mFragmentList;
notifyDataSetChanged();
}
@Override
public int getCount() {
return this.mFragmentList.size();
}
public int getItemPosition(Object object) {
return POSITION_NONE;
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
}
但是,這樣做有一個(gè)缺點(diǎn),那就是會(huì)造成不必要的浪費(fèi),會(huì)影響性能。還有就是必須使用一個(gè) List 緩存所有的 Fragment,這也得占用不少內(nèi)存,那接下來看看如何去優(yōu)化。
優(yōu)化方案:通過 Tag 獲取緩存的 Fragment
首先我們應(yīng)該知道 FragmentManager 是通過 Tag 找相應(yīng)的 Fragment,從而達(dá)到緩存 Fragment 的目的。如果可以找到,就不會(huì)創(chuàng)建新的 Fragment,F(xiàn)ragment 的 onCreate()、onCreateView() 等方法都不會(huì)再次調(diào)用。那優(yōu)化的思路就有了:
首先,需要緩存所有 Fragment 的 Tag:
private List<String> mTagList; // 用來存放所有的 Tag
// 生成 Tag:直接從 FragmentPageAdapter 源碼里拷貝 Fragment 生成 Tag 的方法
private String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
// 將 Tag 緩存到 List 中
@Override
public Object instantiateItem(ViewGroup container, int position) {
mTagList.add(position, makeFragmentName(container.getId(),
(int) getItemId(position)));
return super.instantiateItem(container, position);
}
其次,在更新 Fragment 時(shí)使用相應(yīng)的 Tag 去 FragmentManamager 中找相應(yīng)的 Fragment,如果存在就直接更新:
public void update(int position, String str) {
Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
if (fragment == null) return;
if (fragment instanceof FragmentTest) {
((FragmentTest)fragment).update(str);
}
notifyDataSetChanged();
}
該方法需要自行在 Fragment 中提供。
最后,對(duì)于動(dòng)態(tài)改變 ViewPager 中 Fragment 的數(shù)量,如果是添加那沒什么要注意的,但是刪除就有點(diǎn)棘手,這里給出實(shí)例代碼:
public void remove(int position) {
mDataList.remove(position);
isDataSetChange = true;
Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
mTagList.remove(position);
if (fragment == null) {
notifyDataSetChanged();
return;
}
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
fragmentTransaction.remove(fragment);
fragmentTransaction.commit();
mFragmentManager.executePendingTransactions();
notifyDataSetChanged();
}
(3)“愛閱”中的解決方案
“愛閱”中的處理方式和上述優(yōu)化的方式很像,但是又做了進(jìn)一步的優(yōu)化,代碼如下所示:
public void updateView(String pagerKey) {
for (int i = 0; i < mViewPagerAdapter.getCount(); i++) {
Fragment fragment = getSupportFragmentManager().findFragmentByTag(mViewPagerAdapter.getFragmentTag(R.id.viewpager_main, i));
if (null != fragment) {
String categoryKey = DataCache.getInstance().getCategory(pagerKey).getNextKey(i);
if (categoryKey != null) {
Category viewPagerCategory = DataCache.getInstance().getCategory(categoryKey);
ViewPagerFragment viewPagerFragment = (ViewPagerFragment) fragment;
viewPagerFragment.setCategoryData(viewPagerCategory);
}
}
}
}
public String getFragmentTag(int viewPagerId, int fragmentPosition) {
return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
}
可以看到,我們通過getSupportFragmentManager().findFragmentByTag()方法去ViewPager中找到當(dāng)前緩存的Fragment,經(jīng)過非空判斷后到數(shù)據(jù)源中獲取要更新的數(shù)據(jù),然后把對(duì)應(yīng)的Fragment中的數(shù)據(jù)進(jìn)行更新,以此來實(shí)現(xiàn)界面的更新。這樣做的好處是:不用ViewPager進(jìn)行操作,從而使數(shù)據(jù)的處理輕量級(jí),不必因?yàn)樗⑿陆缑娑倪^多的CPU資源。
(4)FragmentStatePagerAdapter的解決方案
FragmentStatePagerAdapter 與 FragmentPagerAdapter 類似,這兩個(gè)類都繼承自 PagerAdapter。但是和 FragmentPagerAdapter 不一樣的是,F(xiàn)ragmentStatePagerAdapter 只保留當(dāng)前頁(yè)面,當(dāng)頁(yè)面離開視線后就會(huì)被消除并釋放其資源;而在頁(yè)面需要顯示時(shí),生成新的頁(yè)面(這和 ListView 的實(shí)現(xiàn)一樣)。這種方式的好處就是當(dāng)擁有大量的頁(yè)面時(shí),不必在內(nèi)存中占用大量的內(nèi)存。
public class FSPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<Fragment> mFragmentList;
public FSPagerAdapter(FragmentManager fm, List<Integer> types) {
super(fm);
updateData(types);
}
public void updateData(List<Integer> dataList) {
ArrayList<Fragment> fragments = new ArrayList<>();
for (int i = 0, size = dataList.size(); i < size; i++) {
Log.e("FPagerAdapter1", dataList.get(i).toString());
fragments.add(FragmentTest.instance(dataList.get(i)));
}
setFragmentList(fragments);
}
private void setFragmentList(ArrayList<Fragment> fragmentList) {
if(this.mFragmentList != null){
mFragmentList.clear();
}
this.mFragmentList = fragmentList;
notifyDataSetChanged();
}
@Override
public int getCount() {
return this.mFragmentList.size();
}
public int getItemPosition(Object object) {
return POSITION_NONE;
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
}
上述代碼是對(duì)該情況下的一種解決方式,對(duì)應(yīng)的解釋如下:
1、緩存所有的 Fragment
使用一個(gè) List 將數(shù)據(jù)源對(duì)應(yīng)的 Fragment 都緩存起來
2、更新數(shù)據(jù)源,刷新 Fragment
當(dāng)有數(shù)據(jù)源更新的時(shí)候,從 List 中取出相應(yīng)的 Fragment,然后刷新 Adapter
3、刪除數(shù)據(jù)時(shí),刪除 List 中對(duì)應(yīng)的 Fragment
當(dāng)數(shù)據(jù)源中刪除某項(xiàng)時(shí),將 List 中對(duì)應(yīng)的 Fragment 也刪除,然后刷新 Adapter
總結(jié)
關(guān)于 ViewPager 的使用和 ViewPager 數(shù)據(jù)源刷新的問題到此我們就實(shí)現(xiàn)完畢了,其中 ViewPager 數(shù)據(jù)源刷新的問題比較麻煩的地方是從數(shù)據(jù)源中刪除數(shù)據(jù)的情況,這和 ViewPager 的實(shí)現(xiàn)方式有關(guān),我們?cè)诮鉀Q該問題的時(shí)候要分具體情況來采取不同的方案。
至此為止,我們今天一半的工作量就已經(jīng)完成了,接下來的時(shí)間我們一起去結(jié)合 TabLayout 實(shí)現(xiàn)頂部的導(dǎo)航功能,我們下篇見!
聯(lián)系方式: