Android開發之漫漫長途 Fragment番外篇——TabLayout+ViewPager+Fragment

該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡可能按照先易後難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深刻理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關知識,另外也借鑑了其餘的優質博客,在此向各位大神表示感謝,膜拜!!!android


前言

上一篇文章中咱們使用底部導航+Fragment的方式實現了Android主流App中大都存在的設計。並命名其爲「Fragment最佳實踐」,做爲想到單獨使用Fragment的用戶來講,這個說法並不誇大,它解決了許多用戶在使用Fragment時產生的這樣那樣可見或不可見的問題。不過Fragment還有其餘的使用方式,就是咱們本章要介紹的。(原本是介紹ListView的,等着ListView的讀者很差意思了,我會很快更新的。)緩存

注:爲何臨時插入這一章,由於有讀者在上一篇文章中評論了,我以爲大有道理,感謝

這裏我就不打碼了,,哈哈哈哈網絡

TabLayout

TabLayout的靜態使用

TabLayout是Android 5.0以後Google提供的一系列Material Design設計規範中的一個控件。咱們在佈局文件中能夠這樣使用
app

<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,其內可包含0到n個TabItem,這個TabItem就是咱們常用的標籤,其是個自定義View
,這樣咱們就定義了一個包含3個標籤頁的TabLayout。其運行結果以下圖:ide

TabLayout的動態使用

在佈局文件中咱們能夠很方便定義頂部/底部 導航的佈局。咱們來看一下在代碼中的使用函數

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選擇事件監聽
        mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {//當標籤被選擇時回調
                
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {//當標籤從選擇變爲非選擇時回調

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {//當標籤被從新選擇時回調

            }
        });
        
    }
}

關於運行結果我就不上圖了,跟上面的運行結果是同樣的。佈局

TabLayout的更多屬性

關於TabLayout的更多屬性以及使用的說明請查看其官方文檔。在這裏咱們只關心TabLayout+ViewPager的化學反應,這個組合也是咱們日常在開發中使用最多的。在此以前咱們先介紹ViewPager優化

ViewPager

先看看官方對ViewPager的說明ui

/*
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是一個佈局管理類,這個類呢容許用戶左右翻轉頁面。你必須實現一個PagerAdapter來生成這些顯示的頁面。ViewPager常常和Fragment一塊兒使用。並且呢Google很是貼心的提供了兩個類FragmentPagerAdapter和FragmentStatePagerAdapter來應付那些通常場景。this

其實從ViewPager的說明中,咱們基本上就能知道ViewPager是什麼以及如何使用了。

PagerAdapter

ViewPager繼承於ViewGroup,官方指導中就說了,你要本身實現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 {
}

其實咱們在看一個不太瞭解的類的時候,經過源碼上的關於這個類的說明就能夠知道不少信息了。關於PagerAdapter的說明就是如此。
先說了一下PagerAdapter的做用,是一個基類提供適配器給ViewPager中的頁面,若是你想使用特定的實現類,那麼你能夠看兩個類FragmentPagerAdapter和FragmentStatePagerAdapter,這兩個類繼承了PagerAdapter,並實現了其抽象方法。

後面一段的意思是你若是想自定義你本身的PagerAdapter,那麼你最少要實現這4個方法

  1. instantiateItem(ViewGroup, int)

  2. destroyItem(ViewGroup, int, Object)

  3. getCount()

  4. isViewFromObject(View, Object)

下面咱們以代碼的形式,說明這4個方法的含義以及如何使用

private class MyViewPagerAdapter extends PagerAdapter {

    /**
      * 獲取View的總數
      *
      * @return View總數
      */
    @Override
    public int getCount() {
        return 0;
    }

    
    /**
     * 爲給定的位置建立相應的View。建立View以後,須要在該方法中自行添加到container中。
     *
     * @param container ViewPager自己
     * @param position  給定的位置
     * @return 提交給ViewPager進行保存的實例對象
     */
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        return super.instantiateItem(container, position);
    }
    
    /**
     * 給定的位置移除相應的View。
     *
     * @param container ViewPager自己
     * @param position  給定的位置
     * @param object    在instantiateItem中提交給ViewPager進行保存的實例對象
     */
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
    }
 
    
    /**
     * 確認View與實例對象是否相互對應。ViewPager內部用於獲取View對應的ItemInfo。
     *
     * @param view   ViewPager顯示的View內容
     * @param object 在instantiateItem中提交給ViewPager進行保存的實例對象
     * @return 是否相互對應
     */
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return false;
    }
    

}

這4個方法是必須的,,另外還有一些不是必須,可是可能會用到的

/**
 * 當ViewPager的內容有所變化時,進行調用。
 *
 * @param container ViewPager自己
 */
@Override
public void startUpdate(ViewGroup container) {
    super.startUpdate(container);
}


/**
 * ViewPager調用該方法來通知PageAdapter當前ViewPager顯示的主要項,提供給用戶對主要項進行操做的方法。
 *
 * @param container ViewPager自己
 * @param position  給定的位置
 * @param object    在instantiateItem中提交給ViewPager進行保存的實例對象
 */
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    super.setPrimaryItem(container, position, object);
}


/**
 * 較多的用於Design庫中的TabLayout與ViewPager進行綁定時,提供顯示的標題。
 *
 * @param position 給定的位置
 * @return 顯示的標題
 */
@Override
public CharSequence getPageTitle(int position) {
    return super.getPageTitle(position);
}

FragmentPagerAdapter

上面呢只是列舉說明了一下PagerAdapter,看起來有些枯燥,都是些說明,那麼咱們來看一下實踐,ViewPager通暢跟Fragment一塊兒使用,即其所管理的頁面通暢是Fragment,因此Google提供了兩個適配器FragmentPagerAdapter和FragmentStatePagerAdapter,咱們這節分析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;
    }

    /**
     *抽象方法,看來這個函數要子類本身實現了
     *
     * @param position ViewPager中Item的位置
     * @return 位置相關聯的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");
        }
    }

    /**
     * 爲給定的位置建立相應的fragment。建立fragment以後,須要在該方法中自行添加到container中。
     *
     * @param container ViewPager自己
     * @param position  給定的位置
     * @return 提交給ViewPager進行保存的實例對象,這裏是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;
    }

    /**
     * 移除給定的位置相應的fragment。
     *
     * @param container ViewPager自己
     * @param position  給定的位置
     * @param object    在instantiateItem中提交給ViewPager進行保存的實例對象
     */
    @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

/**
     * 爲給定的位置建立相應的fragment。建立fragment以後,須要在該方法中自行添加到container中。
     *
     * @param container ViewPager自己
     * @param position  給定的位置
     * @return 提交給ViewPager進行保存的實例對象,這裏是Fragment
     */
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        //開啓事務
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        //獲得指定位置Item的ID
        final long itemId = getItemId(position);

       //根據id和ViewPager的ID生成item的name
        String name = makeFragmentName(container.getId(), itemId);

        //以name爲Tag查找對應的Fragment
        Fragment fragment = mFragmentManager.findFragmentByTag(name);


        if (fragment != null) {//若是找到了
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            //調用事務的attach
            mCurTransaction.attach(fragment);
        } else {//沒找到
            //經過咱們重寫的getItem方法獲得相應fragment
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);

            //調用事務的add方法,並設置Tag
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        //若是frament不等於當前主要的Item

        if (fragment != mCurrentPrimaryItem) {
            //設置其Menu不可見
            fragment.setMenuVisibility(false);
            //設置其不可見
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }

instantiateItem方法主要功能是爲ViewPager生成Item。
那麼destroyItem方法的主要功能是銷燬ViwePager內的Item

@Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        
        //調用事務的detach方法
        mCurTransaction.detach((Fragment)object);
    }

FragmentStatePagerAdapter

關於FragmentStatePagerAdapter,讀者可自行分析,代碼也不長。須要注意的地方是,二者對於destroyItem的不一樣實現

@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);

    //調用事務的remove方法
    mCurTransaction.remove(fragment);
}

小結

ViewPager是個ViewGroup,與其餘佈局LinearLayout或者其餘任意的ViewGroup並沒有本質的不一樣,它被Google建議與Fragment結伴使用,也是說ViewPager所包裹的是Fragment佈局。ViewPager須要適配器PagerAdapter操做Fragment,這一點就像ListView須要適配器操做其內部的Item同樣。

適配器PagerAdapter是個抽象類,而且依照官方說明,咱們必須至少實現其4個重要方法。4個方法可能太多,因此Google提供了FragmentPagerAdapter以及FragmentStatePagerAdapter,這兩個也是抽象類,不過咱們的自定義Adapter只須要實現其中的getItem(int position)方法便可。

關於FragmentPagerAdapter以及FragmentStatePagerAdapter的不一樣,我這裏再總結一下。FragmentPagerAdapter銷燬item的時候最終調用FragmentTransaction的detach()方法,使用detach()會將view從viewtree中刪除,和FragmentStatePagerAdapter中使用的remove()不一樣,此時fragment的狀態依然保持着,在使用attach()時會再次調用onCreateView()來重繪視圖,注意使用detach()後fragment.isAdded()方法將返回false。

實例

更改後的TabActivity對應的佈局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/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設置Adapter
        mViewPager.setAdapter(adapter);

        //爲ViewPager添加頁面改變監聽
        mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));

        //爲TabLayout添加Tab選擇監聽
       
        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的數量
    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的數量
     */
    @Override
    public int getCount() {
        return nNumOfTabs;
    }
}

ViewPager預加載與網絡請求

ViewPager的預加載機制

ViewPager可經過setOffscreenPageLimit(int limit)函數設置ViewPager預加載的View數目

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();
    }
}

能夠看到該函數的源碼,當咱們傳入的limit<1時,limit仍是被設置爲1,當limit與成員變量mOffscreenPageLimit的值不一樣時(成員變量mOffscreenPageLimit的默認值爲1),更新成員變量mOffscreenPageLimit的值,而後調用populate()函數。

而這個populate()函數就是給咱們的ViewPager準備緩存頁面並顯示當前頁面用的。

假如說我採用下面的方法調用setOffscreenPageLimit(2),此時ViewPager的簡單示意圖

注:從上面的代碼也能夠看出ViewPager最少會預加載一個頁面。在本例中,也是咱們在顯示TAB1的時候,ViewPager已經加載了TAB2,具體方式是經過instantiateItem方法,該方法內部調用了咱們重寫的getItem方法,TAB2所表示的Fragment的onCreateView等相關生命週期方法會被回調。

ViewPager的網絡請求

ViewPager的預加載機制其實在某些時候是個很讓人不爽的問題,好比咱們在Fragment作網絡請求數據的時候,咱們網絡請求的代碼一般會放在onCreateView中,咱們只是打開第1個Fragment,可是因爲ViewPager會加載第2個Fragment,可能也執行了第2個Fragment的網絡請求代碼。

而避免上述問題的主要依靠

public void setUserVisibleHint(boolean isVisibleToUser)

setUserVisibleHint(boolean isVisibleToUser)是Fragment中的一個回調函數。當前Fragment可見時,setUserVisibleHint()回調,其中isVisibleToUser=true。當前Fragment由可見到不可見或實例化時,setUserVisibleHint()回調,其中isVisibleToUser=false。

setUserVisibleHint(boolean isVisibleToUser)調用時機

  1. 在Fragment實例化,即在ViewPager中,因爲ViewPager默認會預加載左右兩個頁面。此時預加載頁面回調的生命週期流程:setUserVisibleHint() -->onAttach() --> onCreate()-->onCreateView()--> onActivityCreate() --> onStart() --> onResume()

    此時,setUserVisibleHint() 中的參數爲false,由於不可見。

  2. 在Fragment可見時,即ViewPager中滑動到當前頁面時,由於已經預加載過了,以前生命週期已經走到onResume() ,因此如今只會回調:setUserVisibleHint()。

    此時,setUserVisibleHint() 中的參數爲true,由於可見。

  3. 在Fragment由可見變爲不可見,即ViewPager由當前頁面滑動到另外一個頁面,由於還要保持當前頁面的預加載過程,因此只會回調:setUserVisibleHint()。

    此時,setUserVisibleHint() 中的參數爲false,由於不可見。

  4. 由TabLayout直接跳轉到一個未預加載的頁面,此時生命週期的回調過程:setUserVisibleHint() -->setUserVisibleHint() -->onAttach() --> onCreate()-->onCreateView()--> onActivityCreate() --> onStart()
    --> onResume()

    此時回調了兩次setUserVisibleHint() ,一次表明初始化時,傳入參數是false,一次表明可見時,傳入參數是true。這種狀況比較特殊。

總結:不管什麼時候,setUserVisibleHint()都是先於其餘生命週期的調用,而且初始化時調用,可見時調用,由可見轉換成不可見時調用,一共三次時機。

ViewPager的網絡請求的優化實現

咱們在使用ViewPager+Fragment顯示數據的時候,咱們一般會把網絡請求的操做放在onCreateView->onResume之間的生命週期內。這可能帶來的問題咱們上面已經探討了。那麼怎麼解決這個問題呢?

本篇總結

咱們在本篇博客中比較詳細的探討了TabLayout+ViewPager+Fragment的使用,咱們在許多主流App中都能看到這種頂部、底部導航的效果,而且在此基礎上咱們探討了TabLayout+ViewPager+Fragment網絡數據加載問題。

咱們但願Fragment可見時加載網絡數據,不可見時不進行或者取消網絡請求。

public abstract class BaseFragment extends Fragment {
    protected View rootView;

    private Unbinder mUnbinder;
    //當前Fragment是否處於可見狀態標誌,防止因ViewPager的緩存機制而致使回調函數的觸發
    private boolean isFragmentVisible;
    //是不是第一次開啓網絡加載
    public boolean isFirst;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (rootView == null)
            rootView = inflater.inflate(getLayoutResource(), container, false);
        mUnbinder = ButterKnife.bind(this, rootView);
        initView();
        //可見,可是並無加載過
        if (isFragmentVisible && !isFirst) {
            onFragmentVisibleChange(true);
        }
        return rootView;
    }

    //獲取佈局文件
    protected abstract int getLayoutResource();


    //初始化view
    protected abstract void initView();


    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
            isFragmentVisible = true;
        }
        if (rootView == null) {
            return;
        }
        //可見,而且沒有加載過
        if (!isFirst&&isFragmentVisible) {
            onFragmentVisibleChange(true);
            return;
        }
        //由可見——>不可見 已經加載過
        if (isFragmentVisible) {
            onFragmentVisibleChange(false);
            isFragmentVisible = false;
        }
    }


    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mUnbinder.unbind();
    }

    /**
     * 當前fragment可見狀態發生變化時會回調該方法
     * 
     * 若是當前fragment是第一次加載,等待onCreateView後纔會回調該方法,其它狀況回調時機跟 {@link #setUserVisibleHint(boolean)}一致
     * 在該回調方法中你能夠作一些加載數據操做,甚至是控件的操做.
     *
     * @param isVisible true  不可見 -> 可見
     *                  false 可見  -> 不可見
     */
    protected void onFragmentVisibleChange(boolean isVisible) {

    }


}

咱們設計抽象基類BaseFragment,全部的公共行爲咱們均可以在這個基類中定義,那麼咱們的Fragment是否可見就是其中的一種行爲,因此咱們上面重寫了Fragment的setUserVisibleHint方法。

public class GoodsFragment extends BaseFragment {
    
    @Override
    protected void onFragmentVisibleChange(boolean isVisible) {
        if(isVisible){
            //可見,而且是第一次加載
            lazyLoad();
        }else{
            //取消加載
        }
    }

    private void lazyLoad() {
        if (!isFirst) {
            isFirst = true;
        }
    }


    @Override
    protected int getLayoutResource() {
        return R.layout.fragment_goods;
    }

    @Override
    protected void initView() {

    }
}

咱們設計GoodsFragment繼承BaseFragment並重寫其onFragmentVisibleChange以控制自身的網絡請求。


本篇總結

本篇爲讀者介紹了另一種導航頁切換的實現,咱們使用TabLayout+ViewPager+Fragment的方式,其中讀者須要重點理解如下幾點

  1. ViewPager是個ViewGroup,它所關機的佈局就是一般是咱們的Fragment佈局。
  2. ViewPager的預加載機制、可能帶來的問題及如何解決。
  3. 理解PagerAdapter,以及如何實現它
  4. 理解Google提供了兩個特定場景的PagerAdapter實現類FragmentPagerAdapter以及FragmentStatePagerAdapter,以及他們的主要區別

下篇預告

下篇打算往Fragment中加點東西,ListView


此致,敬禮

相關文章
相關標籤/搜索