TabLayout+ViewPager+Fragment實現懶載入完全解決方案
TabLayout+ViewPager+Fragment實現懶載入完全解決方案
開發過程中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的那幾個生命週期來使用,所以懶載入實現起來才有這麼多講究。
相關文章
- Android tabLayout+viewPager+fragment處理懶載入AndroidTabLayoutViewpagerFragment
- Tablayout+Viewpager+Fragment組合使用以及懶載入機制TabLayoutViewpagerFragment
- 圖片懶載入實現
- 圖片懶載入js實現JS
- 滾動載入圖片(懶載入)實現原理
- 手把手實現圖片懶載入+封裝vue懶載入元件封裝Vue元件
- vue-view-lazy:基於vue2.x懶載入解決方案VueView
- [譯] 原生實現圖片懶載入
- React如何實現圖片懶載入React
- 實現圖片懶載入(throttle, debounce)
- 【Spring註解驅動開發】使用@Lazy註解實現懶載入Spring
- Fragment 懶載入實踐Fragment
- 實現圖片懶載入的三種方式
- 深入理解React:懶載入(lazy)實現原理React
- webpack4 系列教程(四): 單頁面解決方案–程式碼分割和懶載入Web
- 懶載入
- vue12-ElementUI tree元件懶載入的實現VueUI元件
- 多層巢狀後的 Fragment 懶載入實現巢狀Fragment
- 基於react的lazy-load懶載入實現React
- 微信小程式--實現圖片懶載入(lazyload)微信小程式
- Vue實現一個圖片懶載入外掛Vue
- 30行Javascript程式碼實現圖片懶載入JavaScript
- spring註解@lazy,bean懶載入SpringBean
- 解決使用jpa的實體物件轉json符串時懶載入問題物件JSON
- 小說APP原始碼的圖片載入方式,懶載入和預載入的實現APP原始碼
- 懶載入和預載入
- 一起來實現圖片滾動懶載入
- python 實現類屬性的懶載入裝飾器Python
- Ribbon - 懶載入
- vue-router懶載入速度緩慢問題及解決方法Vue
- vue3 + vite實現非同步元件和路由懶載入VueVite非同步元件路由
- 完全跨域的單點登入(SSO)解決方案原始碼解析跨域原始碼
- 【譯】懶載入元件元件
- 圖片懶載入
- vue路由懶載入Vue路由
- Vue元件懶載入Vue元件
- Hibernate 之 懶載入
- Vue 的懶載入Vue