Android TabLayout+ViewPager+Fragment實現懶加載完全解決方案

版權聲明:本文為CSDN博主「代碼都tm飛了」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接


開發過程中TabLayout配合ViewPager和Fragment的使用是常用的實現多頁面的方式。但是這種方式存在一些問題:ViewPager會對其中的Fragment進行預加載。也就是說用戶第一次打開第一個界面的時候,不僅第一個界面會進行加載,其他的界面也會進行界面的預加載。這樣就會帶來界面啟動加載慢,浪費系統資源和用戶流量的不好的體驗。而Fragment的懶加載恰好可以解決這個問題.

首先我們來看看其他App的懶加載實現效果,這里以Bilibili客戶端為例:


實現思路:

通過Fragment的setUserVisibleHint方法,這個方法會傳遞一個boolean類型的參數isVisibleToUser,當Fragment的可見性發生變化時回調這個方法并把是否可見傳入參數。所以我們可以在這個方法中進行懶加載。

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser) {
        onLazyLoad();//數據加載操作
    }
}

問題1:

setUserVisibleHint方法在切換界面時會多次調用,而我們只希望他被調用一次,既第一次進入頁面時被調用。

解決:

在Fragment里創建一個boolean類型的 成員變量isFirstLoad,來判斷當前是否為第一次進入界面。并在Fragment初始化的時候將其初始化為true,在setUserVisibleHint方法中進行判斷如果為true就開始加載數據并將其置為false,否則不加載。

private boolean isFirstLoad = true;//初始化變量

@Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isFirstLoad && isVisibleToUser) {
            onLazyLoad();//數據加載操作
            isFirstLoad = false;//改變變量的值
        }
    }

問題2:

setUserVisibleHint是Fragment的可見性變化時回調的方法,而onCreateView是Fragment創建視圖時回調的方法。但是我們無法保證setUserVisibleHint的調用發生在onCreateView(也就是視圖創建)之后。那么我們就是在視圖還沒有創建時進行數據加載,而往往數據的加載會對視圖控件進行操作,那么就會造成空指針的異常發生(因為視圖控件還沒有初始化)。

解決:

既然我們要保證數據加載發生在視圖創建之后,那么我們依然可以通過對isFirstLoad這個成員變量的操作來實現。之前我們是在Fragment創建時就初始化isFirstLoad為true,那么我們這次就可以將他初始化為false然后在onCreateView中將其初始化為true,這樣就能保證視圖創建完成之前不會進行數據加載的操作。

private boolean isFirstLoad = false;//初始化為false

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.XX,container,false);
    //初始化視圖控件...

    isFirstLoad = true;//視圖創建完成,將變量置為true

    return view; 
}

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isFirstLoad && isVisibleToUser) {
            onLazyLoad();//數據加載操作
            isFirstLoad = false;
        }
    }

問題3:

setUserVisibleHint這個方法只在Fragment的可見性改變的時候才會被調用,而如果按照上面的代碼第一次進入頁面之后setUserVisibleHint先被調用,這時視圖還沒有完成創建,所以數據加載操作不會被調用。而之后沒有切換頁面,Fragment的可見性也就不會發生改變了,setUserVisibleHint也就不會被調用了,那么數據加載也就不會被執行了。

解決:

既然要保證在視圖創建完成后要進行一次數據加載,那么就在onCreateView方法中手動調用一次數據加載就好了。不過這里也要進行對isFirstLoad的操作,在數據加載之前要通過getUserVisibleHint()判斷可見性,在加載后還要將isFirstLoad置為false。這樣才足夠嚴謹。

private boolean isFirstLoad = false;

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.XX,container,false);
    //初始化視圖控件...

    isFirstLoad = true;//視圖創建完成,將變量置為true 

    if (getUserVisibleHint()) {//判斷Fragment是否可見
        onLazyLoad();//數據加載操作
        isFirstLoad = false;//將變量置為false
    }
    return view;
}

@Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isFirstLoad && isVisibleToUser) {
            onLazyLoad();//數據加載操作
            isFirstLoad = false;//將變量置為false
        }

    }

完全解決方案:

創建一個抽象父類:BaseLazyLoadFragment,在父類中實現整個懶加載的流程,然后將初始化視圖、初始化事件和數據加載的接口暴露給子類讓子類來實現。完整代碼:

public abstract class BaseLazyLoadFragment extends Fragment {

    private boolean isFirstLoad = false;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {

        View view = initView(inflater, container);//讓子類實現初始化視圖

        initEvent();//初始化事件

        isFirstLoad = true;//視圖創建完成,將變量置為true

        if (getUserVisibleHint()) {//如果Fragment可見進行數據加載
            onLazyLoad();
            isFirstLoad = false;
        }
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        isFirstLoad = false;//視圖銷毀將變量置為false
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isFirstLoad && isVisibleToUser) {//視圖變為可見并且是第一次加載
            onLazyLoad();
            isFirstLoad = false;
        }

    }
    //數據加載接口,留給子類實現
    public abstract void onLazyLoad();
    
    //初始化視圖接口,子類必須實現
    public abstract View initView(LayoutInflater inflater, @Nullable ViewGroup container);
    
    //初始化事件接口,留給子類實現
    public abstract void initEvent();

}

使用:

如果當前的Fragment要用到懶加載功能的話,只要讓他繼承自BaseLazyLoadFragment,并重寫對應的方法就可以了。

效果展示:

這里只有一個Activity,然后通過TabLayout+ViewPager+Fragment來實現多頁面,接著用BaseLazyLoadFragment來實現懶加載。這里用SwipeRefreshLayout的加載和視圖的隱藏/顯示來模擬數據加載過程。

注意:

ViewPager默認只保留當前視圖的前后各一個視圖,其他的視圖會被銷毀。如果不想讓視圖被銷毀要重寫FragmentPagerAdapter的destroyItem方法,并注釋掉原本的代碼。

總結:

其實之所以Fragment的懶加載這么麻煩是因為Fragment的生命周期很特殊,Fragment的幾個生命周期方法都是和他所綁定的Activity的生命周期對應的。但是當Activity可見的時候Fragment卻不一定可見,所以出現了Fragment專有的生命周期方法:setUserVisibleHint()。但是在使用時也不可以脫離Activity的那幾個生命周期來使用,所以懶加載實現起來才有這么多講究。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。