版權聲明:本文為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的那幾個生命周期來使用,所以懶加載實現起來才有這么多講究。