作者:忘了12138 地址:http://www.cnblogs.com/wangle12138/p/8419496.html 聲明:本文是 忘了12138 原創(chuàng)投稿,轉(zhuǎn)發(fā)等請聯(lián)系原作者授權(quán)。
該文章是一個系列文章,是本人在Android開發(fā)的漫漫長途上的一點(diǎn)感想和記錄,我會盡量按照先易后難的順序進(jìn)行編寫該系列。該系列引用了《Android開發(fā)藝術(shù)探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關(guān)知識,另外也借鑒了其他的優(yōu)質(zhì)博客,在此向各位大神表示感謝,膜拜!??!
前言 上一篇文章中我們使用底部導(dǎo)航+Fragment的方式實(shí)現(xiàn)了Android主流App中大都存在的設(shè)計。并命名其為“Fragment最佳實(shí)踐”,作為想到單獨(dú)使用Fragment的用戶來說,這個說法并不夸大,它解決了許多用戶在使用Fragment時產(chǎn)生的這樣那樣可見或不可見的問題。不過Fragment還有其他的使用方式,就是我們本章要介紹的。(本來是介紹ListView的,等著ListView的讀者不好意思了,我會很快更新的。)
注:為什么臨時插入這一章,因?yàn)橛凶x者在上一篇文章中評論了,我覺得大有道理,感謝
這里我就不打碼了,,哈哈哈哈TabLayout TabLayout的靜態(tài)使用 TabLayout是Android 5.0之后Google提供的一系列Material Design設(shè)計規(guī)范中的一個控件。我們在布局文件中可以這樣使用。
<android.support.design.widget.TabLayout android:id ="@+id/tab_layout" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_alignParentBottom ="true" app:tabIndicatorHeight ="0dp" app:tabSelectedTextColor ="@color/colorPrimary" > <android.support.design.widget.TabItem android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="Tab 1" /> <android.support.design.widget.TabItem android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="Tab 2" /> <android.support.design.widget.TabItem android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="Tab 3" /> </android.support.design.widget.TabLayout >
TabLayout間接繼承于ViewGroup,其內(nèi)可包含0到n個TabItem,這個TabItem就是我們經(jīng)常使用的標(biāo)簽,其是個自定義View,這樣我們就定義了一個包含3個標(biāo)簽頁的TabLayout。其運(yùn)行結(jié)果如下圖:
TabLayout的動態(tài)使用 在布局文件中我們可以很方便定義頂部/底部 導(dǎo)航的布局。我們來看一下在代碼中的使用
public class TabActivity extends AppCompatActivity { @BindView (R.id.tab_layout) TabLayout mTabLayout; @BindView (R.id.view_pager) ViewPager mViewPager; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_tab); ButterKnife.bind(this ); mTabLayout.addTab(mTabLayout.newTab().setText("Tab 1" )); mTabLayout.addTab(mTabLayout.newTab().setText("Tab 2" )); mTabLayout.addTab(mTabLayout.newTab().setText("Tab 3" )); //為TabLayout添加Tab選擇事件監(jiān)聽 mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected (TabLayout.Tab tab) {//當(dāng)標(biāo)簽被選擇時回調(diào) } @Override public void onTabUnselected (TabLayout.Tab tab) {//當(dāng)標(biāo)簽從選擇變?yōu)榉沁x擇時回調(diào) } @Override public void onTabReselected (TabLayout.Tab tab) {//當(dāng)標(biāo)簽被重新選擇時回調(diào) } }); } }
關(guān)于運(yùn)行結(jié)果我就不上圖了,跟上面的運(yùn)行結(jié)果是一樣的。
TabLayout的更多屬性 關(guān)于TabLayout的更多屬性以及使用的說明請查看其官方文檔。在這里我們只關(guān)心TabLayout+ViewPager的化學(xué)反應(yīng),這個組合也是我們平常在開發(fā)中使用最多的。在此之前我們先介紹ViewPager
ViewPager 先看看官方對ViewPager的說明
/* Layout manager that allows the user to flip left and right through pages of data. You supply an implementation of a {@link PagerAdapter} to generate the pages that the view shows. ViewPager is most often used in conjunction with {@link android.app.Fragment} There are standard adapters implemented for using fragments with the ViewPager, which cover the most common use cases. These are {@link android.support.v4.app.FragmentPagerAdapter} and {@link android.support.v4.app.FragmentStatePagerAdapter};*/ public class ViewPager extends ViewGroup { }
上面英文的大致意思是ViewPager是一個布局管理類,這個類呢允許用戶左右翻轉(zhuǎn)頁面。你必須實(shí)現(xiàn)一個PagerAdapter來生成這些顯示的頁面。ViewPager經(jīng)常和Fragment一起使用。而且呢Google非常貼心的提供了兩個類FragmentPagerAdapter和FragmentStatePagerAdapter來應(yīng)付那些一般場景。
其實(shí)從ViewPager的說明中,我們基本上就能知道ViewPager是什么以及如何使用了。
PagerAdapter ViewPager繼承于ViewGroup,官方指導(dǎo)中就說了,你要自己實(shí)現(xiàn)PagerAdapter來生成顯示的頁面,那么我們來看看這個PagerAdapter
/** * 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> * / public abstract class PagerAdapter { }
其實(shí)我們在看一個不太了解的類的時候,通過源碼上的關(guān)于這個類的說明就可以知道很多信息了。關(guān)于PagerAdapter的說明就是如此。 先說了一下PagerAdapter的作用,是一個基類提供適配器給ViewPager中的頁面,如果你想使用特定的實(shí)現(xiàn)類,那么你可以看兩個類FragmentPagerAdapter和FragmentStatePagerAdapter,這兩個類繼承了PagerAdapter,并實(shí)現(xiàn)了其抽象方法。
后面一段的意思是你如果想自定義你自己的PagerAdapter,那么你最少要實(shí)現(xiàn)這4個方法
instantiateItem(ViewGroup, int)
destroyItem(ViewGroup, int, Object)
getCount()
isViewFromObject(View, Object)
下面我們以代碼的形式,說明這4個方法的含義以及如何使用
private class MyViewPagerAdapter extends PagerAdapter { /** * 獲取View的總數(shù) * * @return View總數(shù) */ @Override public int getCount () { return 0 ; } /** * 為給定的位置創(chuàng)建相應(yīng)的View。創(chuàng)建View之后,需要在該方法中自行添加到container中。 * * @param container ViewPager本身 * @param position 給定的位置 * @return 提交給ViewPager進(jìn)行保存的實(shí)例對象 */ @Override public Object instantiateItem (ViewGroup container, int position) { return super .instantiateItem(container, position); } /** * 給定的位置移除相應(yīng)的View。 * * @param container ViewPager本身 * @param position 給定的位置 * @param object 在instantiateItem中提交給ViewPager進(jìn)行保存的實(shí)例對象 */ @Override public void destroyItem (ViewGroup container, int position, Object object) { super .destroyItem(container, position, object); } /** * 確認(rèn)View與實(shí)例對象是否相互對應(yīng)。ViewPager內(nèi)部用于獲取View對應(yīng)的ItemInfo。 * * @param view ViewPager顯示的View內(nèi)容 * @param object 在instantiateItem中提交給ViewPager進(jìn)行保存的實(shí)例對象 * @return 是否相互對應(yīng) */ @Override public boolean isViewFromObject (View view, Object object) { return false ; } }
這4個方法是必須的,,另外還有一些不是必須,但是可能會用到的
/** * 當(dāng)ViewPager的內(nèi)容有所變化時,進(jìn)行調(diào)用。 * * @param container ViewPager本身 */ @Override public void startUpdate (ViewGroup container) { super .startUpdate(container); }/** * ViewPager調(diào)用該方法來通知PageAdapter當(dāng)前ViewPager顯示的主要項(xiàng),提供給用戶對主要項(xiàng)進(jìn)行操作的方法。 * * @param container ViewPager本身 * @param position 給定的位置 * @param object 在instantiateItem中提交給ViewPager進(jìn)行保存的實(shí)例對象 */ @Override public void setPrimaryItem (ViewGroup container, int position, Object object) { super .setPrimaryItem(container, position, object); }/** * 較多的用于Design庫中的TabLayout與ViewPager進(jìn)行綁定時,提供顯示的標(biāo)題。 * * @param position 給定的位置 * @return 顯示的標(biāo)題 */ @Override public CharSequence getPageTitle (int position) { return super .getPageTitle(position); }
FragmentPagerAdapter 上面呢只是列舉說明了一下PagerAdapter,看起來有些枯燥,都是些說明,那么我們來看一下實(shí)踐,ViewPager通暢跟Fragment一起使用,即其所管理的頁面通暢是Fragment,所以Google提供了兩個適配器FragmentPagerAdapter和FragmentStatePagerAdapter,我們這節(jié)分析FragmentPagerAdapter。
/** *真是不看不知道,一看嚇一跳。FragmentPagerAdapter也是個抽象類, * */ public abstract class FragmentPagerAdapter extends PagerAdapter { private static final String TAG = "FragmentPagerAdapter" ; private static final boolean DEBUG = false ; private final FragmentManager mFragmentManager; private FragmentTransaction mCurTransaction = null ; private Fragment mCurrentPrimaryItem = null ; public FragmentPagerAdapter (FragmentManager fm) { mFragmentManager = fm; } /** *抽象方法,看來這個函數(shù)要子類自己實(shí)現(xiàn)了 * * @param position ViewPager中Item的位置 * @return 位置相關(guān)聯(lián)的Fragment */ 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" ); } } /** * 為給定的位置創(chuàng)建相應(yīng)的fragment。創(chuàng)建fragment之后,需要在該方法中自行添加到container中。 * * @param container ViewPager本身 * @param position 給定的位置 * @return 提交給ViewPager進(jìn)行保存的實(shí)例對象,這里是Fragment */ @Override public Object instantiateItem (ViewGroup container, int position) { if (mCurTransaction == null ) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); 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; } /** * 移除給定的位置相應(yīng)的fragment。 * * @param container ViewPager本身 * @param position 給定的位置 * @param object 在instantiateItem中提交給ViewPager進(jìn)行保存的實(shí)例對象 */ @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); } @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) { } /** * @param position ViewPager中Item的位置 * @return 唯一的ItemID */ public long getItemId (int position) { return position; } private static String makeFragmentName (int viewId, long id) { return "android:switcher:" + viewId + ":" + id; } }
代碼比較少,總共也就100多行,邏輯也比較清晰明了,我們來著重分析instantiateItem和destroyItem
/** * 為給定的位置創(chuàng)建相應(yīng)的fragment。創(chuàng)建fragment之后,需要在該方法中自行添加到container中。 * * @param container ViewPager本身 * @param position 給定的位置 * @return 提交給ViewPager進(jìn)行保存的實(shí)例對象,這里是Fragment */ @Override public Object instantiateItem (ViewGroup container, int position) { //開啟事務(wù) if (mCurTransaction == null ) { mCurTransaction = mFragmentManager.beginTransaction(); } //得到指定位置Item的ID final long itemId = getItemId(position); //根據(jù)id和ViewPager的ID生成item的name String name = makeFragmentName(container.getId(), itemId); //以name為Tag查找對應(yīng)的Fragment Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null ) {//如果找到了 if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); //調(diào)用事務(wù)的attach mCurTransaction.attach(fragment); } else {//沒找到 //通過我們重寫的getItem方法得到相應(yīng)fragment fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); //調(diào)用事務(wù)的add方法,并設(shè)置Tag mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } //如果frament不等于當(dāng)前主要的Item if (fragment != mCurrentPrimaryItem) { //設(shè)置其Menu不可見 fragment.setMenuVisibility(false ); //設(shè)置其不可見 fragment.setUserVisibleHint(false ); } return fragment; }
instantiateItem方法主要功能是為ViewPager生成Item。 那么destroyItem方法的主要功能是銷毀ViwePager內(nèi)的Item
@Override public void destroyItem (ViewGroup container, int position, Object object ) { if (mCurTransaction == null ) { mCurTransaction = mFragmentManager.beginTransaction(); } //調(diào)用事務(wù)的detach方法 mCurTransaction.detach((Fragment)object ); }
FragmentStatePagerAdapter 關(guān)于FragmentStatePagerAdapter,讀者可自行分析,代碼也不長。需要注意的地方是,兩者對于destroyItem的不同實(shí)現(xiàn)
@Overridepublic 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 ); //調(diào)用事務(wù)的remove方法 mCurTransaction.remove (fragment); }
小結(jié) ViewPager 是個 ViewGroup,與其他布局 LinearLayout 或者其他任意的ViewGroup并無本質(zhì)的不同,它被Google建議與Fragment結(jié)伴使用,也是說ViewPager所包裹的是Fragment布局。ViewPager需要適配器PagerAdapter操作Fragment,這一點(diǎn)就像ListView需要適配器操作其內(nèi)部的Item一樣。
適配器PagerAdapter是個抽象類,并且依照官方說明,我們必須至少實(shí)現(xiàn)其4個重要方法。4個方法可能太多,所以Google提供了FragmentPagerAdapter以及FragmentStatePagerAdapter,這兩個也是抽象類,不過我們的自定義Adapter只需要實(shí)現(xiàn)其中的getItem(int position)方法即可。
關(guān)于FragmentPagerAdapter以及FragmentStatePagerAdapter的不同,我這里再總結(jié)一下。FragmentPagerAdapter銷毀item的時候最終調(diào)用FragmentTransaction的detach()方法,使用detach()會將view從viewtree中刪除,和FragmentStatePagerAdapter中使用的remove()不同,此時fragment的狀態(tài)依然保持著,在使用attach()時會再次調(diào)用onCreateView()來重繪視圖,注意使用detach()后fragment.isAdded()方法將返回false。
實(shí)例 更改后的TabActivity對應(yīng)的布局文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android ="http://schemas./apk/res/android" xmlns:app ="http://schemas./apk/res-auto" android:layout_width ="match_parent" android:layout_height ="match_parent" android:fitsSystemWindows ="true" <!--ViewPager-- > <android.support.v4.view.ViewPager android:id ="@+id/view_pager" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_alignParentTop ="true" > </android.support.v4.view.ViewPager > <!--分割線--> <ImageView android:id ="@+id/image_1" android:layout_width ="match_parent" android:layout_height ="1dp" android:background ="#919292" android:layout_above ="@+id/tab_layout" /> <!--TabLayout--> <android.support.design.widget.TabLayout android:id ="@+id/tab_layout" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_alignParentBottom ="true" app:tabIndicatorHeight ="0dp" app:tabSelectedTextColor ="@color/colorPrimary" > </android.support.design.widget.TabLayout > </RelativeLayout >
更改后的TabActivity
public class TabActivity extends AppCompatActivity { @BindView (R.id.tab_layout) TabLayout mTabLayout; @BindView (R.id.view_pager) ViewPager mViewPager; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_tab); ButterKnife.bind(this ); mTabLayout.addTab(mTabLayout.newTab().setText("Tab 1" )); mTabLayout.addTab(mTabLayout.newTab().setText("Tab 2" )); mTabLayout.addTab(mTabLayout.newTab().setText("Tab 3" )); //自定義的Adapter繼承自FragmentPagerAdapter final PagerAdapter adapter = new PagerAdapter (getSupportFragmentManager(), mTabLayout.getTabCount()); //ViewPager設(shè)置Adapter mViewPager.setAdapter(adapter); //為ViewPager添加頁面改變監(jiān)聽 mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout)); //為TabLayout添加Tab選擇監(jiān)聽 mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected (TabLayout.Tab tab) { mViewPager.setCurrentItem(tab.getPosition()); } @Override public void onTabUnselected (TabLayout.Tab tab) { } @Override public void onTabReselected (TabLayout.Tab tab) { } }); } }
而我們自定義的MyPagerAdapter也非常簡單
public class MyPagerAdapter extends FragmentPagerAdapter { //fragment的數(shù)量 int nNumOfTabs; public MyPagerAdapter (FragmentManager fm, int nNumOfTabs) { super (fm); this .nNumOfTabs=nNumOfTabs; } /** * 重寫getItem方法 * * @param position 指定的位置 * @return 特定的Fragment */ @Override public Fragment getItem (int position) { switch (position) { case 0 : GoodsFragment tab1=new GoodsFragment(); return tab1; case 1 : CategoryFragment tab2=new CategoryFragment(); return tab2; case 2 : TaskFragment tab3=new TaskFragment(); return tab3; } return null ; } /** * 重寫getCount方法 * * @return fragment的數(shù)量 */ @Override public int getCount () { return nNumOfTabs; } }
ViewPager預(yù)加載與懶加載 ViewPager的預(yù)加載機(jī)制 ViewPager可通過setOffscreenPageLimit(int limit)函數(shù)設(shè)置ViewPager預(yù)加載的View數(shù)目
public void setOffscreenPageLimit(int limit ) { //DEFAULT_OFFSCREEN_PAGES=1 if (limit < DEFAULT_OFFSCREEN_PAGES) { Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + DEFAULT_OFFSCREEN_PAGES); limit = DEFAULT_OFFSCREEN_PAGES; } if (limit != mOffscreenPageLimit) { mOffscreenPageLimit = limit ; populate(); } }
本篇總結(jié) 我們在本篇博客中比較詳細(xì)的探討了TabLayout+ViewPager+Fragment的使用,我們在許多主流App中都能看到這種頂部、底部導(dǎo)航的效果,并且在此基礎(chǔ)上我們探討了TabLayout+ViewPager+Fragment懶加載問題。
下篇預(yù)告 下篇打算往Fragment中加點(diǎn)東西,ListView