1. 程式人生 > >fragment 懶載入2

fragment 懶載入2

效果

老規矩,先來看看效果


效果圖

ANDROID和福利兩個Fragment是設定的Fragment可見時載入資料,也就是懶載入。圓形的旋轉載入圖示只有一個,所以,如果當前Fragment正處於載入狀態,在離開該Fragment時需要隱藏載入動畫,因為另一個Fragment並不一定處於載入狀態,當返回Fragment時,如果還是處於載入狀態,則要可以實現自動顯示載入動畫,如果資料已經載入完畢則不需要再顯示出來。

以上效果就是今天要介紹和分享的,那麼開始往下看吧。

懶載入

懶載入意思也就是當需要的時候才會去載入

那麼,為什麼Fragment需要懶載入呢,一般我們都會在onCreate()

或者onCreateView()裡去啟動一些資料載入操作,比如從本地載入或者從伺服器載入。大部分情況下,這樣並不會出現什麼問題,但是當你使用ViewPager + Fragment的時候,問題就來了,這時就應該考慮是否需要實現懶載入了。

ViewPager + Fragment 的坑

ViewPager為了讓滑動的時候可以有很好的使用者的體驗,也就是防止出現卡頓現象,因此它有一個快取機制。預設情況下,ViewPager會提前建立好當前Fragment旁的兩個Fragment,舉個例子說也就是如果你當前顯示的是編號3的Fragment,那麼其實編號2和4的Fragment也已經建立好了,也就是說這3個Fragment

都已經執行完 onAttach() -> onResume() 這之間的生命週期函數了。


日誌圖1

本來Fragment的 onResume()表示的是當前Fragment處於可見且可互動狀態,但由於ViewPager的快取機制,它已經失去了意義,也就是說我們只是打開了“福利”這個Fragment,但其實“休息視訊”和“拓展資源”這兩個Fragment的資料也都已經載入好了。

如果載入資料的操作都比較耗時或者都是類似圖片的佔用大量記憶體,這時就應該考慮想想是否該實現懶載入。也就是,當我開啟哪個Fragment的時候,它才會去載入資料。

懶載入實現?

setUserVisibleHint(boolean isVisibleToUser) 可以?

當你去網上查詢相關資料時,你會發現很多人推薦說把載入資料的操作放在這個函式裡,isVisibleToUser表示當前Fragment是否可見。那麼,是否真的可以就這樣做呢?先來看個日誌:


日誌圖2

題主是從 DayDataFragment 跳轉到 MeiziDataFragment 的,所以可以看到日誌裡面:DayDataFragment打出了false,表示它不可見了。而MeiziDataFragment卻先打出了false,然後才打出true,這是因為setUserVisibleHint()在Fragment例項化時會先呼叫一次,並且預設值是false,當選中當前顯示的Fragment時還會再呼叫一次。

所以,看上面的日誌,除了DayDataFragment外,其他三個Fragment均沒有例項化,所以當開啟MeiziDataFragment時,因為ViewPager的快取機制,會同時建立三個Fragment的例項,所以列印了三條isVisibleToUeser: false的日誌,因為選中的是MeiziDataFragment,所以它還會觸發一次setUserVisibleHint(),並且打印出true。

那麼,是否可以在setUserVisibleHint(boolean isVisibleToUser)裡進行資料載入操作來實現懶載入呢?

可以是可以,如果你只是需要資料的懶載入的話,但如果你還有以下的需求,那麼這種方式就不行了:

1、如果你在Fragment可見時需要進行一些控制元件的操作,比如顯示載入控制元件
2、如果你還需要在Fragment從 “可見 -> 不可見” 時進行一些操作的話,比如取消載入控制元件顯示

這邊再提一下,setUserVisibleHint()可能會在Fragment的生命週期之外被呼叫,也就是可能在view建立前就被呼叫,也可能在destroyView後被呼叫,所以如果涉及到一些控制元件的操作的話,可能會報 null 異常,因為控制元件還沒初始化,或者已經摧毀了。

進一步封裝

題主稍微進行了一些封裝,自定義了一個新的回撥函式 onFragmentVisibleChange(boolean isVisible) ,可以實現的效果有:

1、只有兩種情況會觸發該函式
2、一種是Fragment從“不可見 -> 可見” 時觸發,並傳入 isVisible = true
3、一種是Fragment從“可見 -> 不可見” 時觸發,並傳入 isVisible = false
4、可以在該函式內進行控制元件的操作,不會報null異常

日誌圖3

題主這次仍舊是從DayDataFragment 跳轉到 MeiziDataFragment, 但跟上上面的日誌圖片不同,這裡只打印了兩條日誌,也就是說即使有三個Fragment被例項化了,但只有顯示的那個Fragment和離開的那個Fragment才會觸發回撥函式,這樣就可以支援我們在可見狀態變化時進行一些操作,因為不會有多餘的false觸發。

另外,因為ViewPager快取機制,所以題主進行了view的複用,防止onCreateView()重複的建立view,其實也就是將view設定為成員變數,建立view時先判斷是否為null。因為ViewPager裡對Fragment的回收和建立時,如果Fragment已經建立過了,那麼只會呼叫 onCreateView() -> onDestroyView() 生命函式,onCreate()和onDestroy並不會觸發,所以關於變數的初始化和賦值操作可以在onCreate()裡進行,這樣就可以避免重複的操作。

程式碼

2016-04-21 更新:該部落格封裝的懶載入實現有些不足,比如不支援資料只有第一次開啟Fragment時才進行載入的應用場景,因此重新寫了篇部落格,可以移步至此觀看:再來一篇Fragment的懶載入(只加載一次哦)

最後附上程式碼,另外注意一下,題主是從專案裡抽出程式碼,進行一些修改,讓它儘量可以直接複製貼上使用,但並沒有進行過測試,所以如果不行的話可以留言,題主會檢視。或者你直接到我原專案裡去檢視,程式碼已託管至Github上,因為專案是針對具體需求的,所以類裡面會增加很多其他無關的程式碼。再或者,你可以嘗試自己進行封裝下,程式碼很少,不到50行,理解思路就行了。

/**
 * Created by dasu on 2016/9/27.
 * https://github.com/woshidasusu/Meizi
 *
 * Viewpager + Fragment情況下,fragment的生命週期因Viewpager的快取機制而失去了具體意義
 * 該抽象類自定義一個新的回撥方法,當fragment可見狀態改變時會觸發的回撥方法,介紹看下面
 *
 * @see #onFragmentVisibleChange(boolean)
 */
public abstract class ViewPagerFragment extends Fragment {

    /**
     * rootView是否初始化標誌,防止回撥函式在rootView為空的時候觸發
     */
    private boolean hasCreateView;

    /**
     * 當前Fragment是否處於可見狀態標誌,防止因ViewPager的快取機制而導致回撥函式的觸發
     */
    private boolean isFragmentVisible;

    /**
     * onCreateView()裡返回的view,修飾為protected,所以子類繼承該類時,在onCreateView裡必須對該變數進行初始化
     */
    protected View rootView;

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.d(getTAG(), "setUserVisibleHint() -> isVisibleToUser: " + isVisibleToUser);
        if (rootView == null) {
            return;
        }
        hasCreateView = true;
        if (isVisibleToUser) {
            onFragmentVisibleChange(true);
            isFragmentVisible = true;
            return;
        }
        if (isFragmentVisible) {
            onFragmentVisibleChange(false);
            isFragmentVisible = false;
        }
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initVariable();
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if (!hasCreateView && getUserVisibleHint()) {
            onFragmentVisibleChange(true);
            isFragmentVisible = true;
        }
    }

    private void initVariable() {
        hasCreateView = false;
        isFragmentVisible = false;
    }

    /**************************************************************
     *  自定義的回撥方法,子類可根據需求重寫
     *************************************************************/

    /**
     * 當前fragment可見狀態發生變化時會回撥該方法
     * 如果當前fragment是第一次載入,等待onCreateView後才會回撥該方法,其它情況回撥時機跟 {@link #setUserVisibleHint(boolean)}一致
     * 在該回調方法中你可以做一些載入資料操作,甚至是控制元件的操作,因為配合fragment的view複用機制,你不用擔心在對控制元件操作中會報 null 異常
     *
     * @param isVisible true  不可見 -> 可見
     *                  false 可見  -> 不可見
     */
    protected void onFragmentVisibleChange(boolean isVisible) {
        Log.w(getTAG(), "onFragmentVisibleChange -> isVisible: " + isVisible);
    }
}

用法

新建類ViewPagerFragment,將上面程式碼複製貼上進去,新增需要的import語句 -> 新建你需要的Fragment類,繼承ViewPagerFragment,在onCreateView()裡對rootView進行初始化 -> 重寫onFragmentVisibleChange(),在這裡進行你需要的操作,比如資料載入,控制顯示等。

public class MyFragment extends ViewPagerFragment{

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (rootView == null) {
            rootView = inflater.inflate(R.layout.fragment_android, container, false);

        }
        return rootView;
    } 

    @Override
    protected void onFragmentVisibleChange(boolean isVisible) {
        super.onFragmentVisibleChange(isVisible);
        if (isVisible) {
        //   do things when fragment is visible    
        //    if (ListUtils.isEmpty(mDataList) && !isRefreshing()) {
        //        setRefresh(true);
        //        loadServiceData(false);
            } else {
        //        setRefresh(false);
            }
        }
    }
}

PS

以上就是這次的內容了,最近題主想利用 Gank公開的api,做一個類似於Meizi的應用出來,雖然這個App已經有無數人都做過了,但確實是一個很值得學習的專案,題主仍然是小白一個,所以還是好好學習下。drakeet的Meizi專案用到了很多高階技術,比如Rxjava之類的,題主看不懂,其他Github上一些比較出名的Meizi App要麼是MVP架構,要麼還是用到了目前小白的我看不懂的技術,所以這次就決定自己用最基礎的MVC,一些簡單常用的第三方庫來做這個App,畢竟路要一步一步走,如果這個完成了,收穫和體驗應該會很多(這不就收穫了這篇隨筆了嗎O(∩_∩)O),所以,如果有興趣的話,歡迎Start,歡迎指點,歡迎拍磚,大家一起學習進步。