ViewPager嵌套Fragment懶加載處理
需要懶加載的情況分析
- fragment可見:
分爲兩種(1)第一次可見(2)來回切換後可見
第一次可見:意味着加載的數據更多,網絡請求更多。
來回切換後可見:可能在第一次網絡請求後將數據緩存在本地,那麼只需要讀取緩存即可; - fragment不可見:
當fragment不可見時,需要中斷網絡請求(或者輪播圖Banner等)
綜合以上兩個可見和不可見狀態,能夠感知的生命週期只有onResume和onPasue,外加一個setUserVisibleHint方法,那麼懶加載也就是在這三個方法中分發正確的事件,可以將第一次加載,懶加載,中斷,這三個操作抽出成爲方法,由子類實現,父類負責分發。
結合生命週期的坑點
上面的兩種情況分析,看上去非常的簡單,實際上懶加載也就是完成了以上的工作,但是,實際代碼中,會有坑,onResume和onPause。
先說onResume需要考慮的問題:
假設,現在有三個tab一個viewpager,每個tab對應着一個fragment。當tab1切換到tab2時,由於viewpager中的setOffscreenPageLimit默認值爲1,也就意味着tab3此時會走onResume的生命週期;
再比如:Activity1中有多個fragment,此時有Activity1跳轉到Activity2,那麼Activity1中會緩存多個Fragment,當由Activity2返回Activity1時,會走fragment的onResume生命週期;
那麼onResume要做如下處理:
僞代碼:
public void onResume() {
super.onResume();
//這個if是爲了解決tab來回切換的問題,當切換到tab2時,tab3已經執行onResume,但是view還沒創建
//所以需要增加一層判斷,當第一次可見時,再去分發事件
if(如果第一次可見){
//由於activity切換時,fragment不會走setUserVisibleHint方法,所以也需要增加一層判斷
if (沒有hidden && 當前狀態不可見 && getUserVisibleHint()){
//分發可見事件
}
}
}
解決完onResume,繼續解決onPause
onPause不需要onResume裏的第一層判斷,因爲只要執行onPause那說明已經執行過了onCreate以及onResume。同樣,當Activity1跳轉到Activity2時,1裏面的所有fragment都會執行onPause,那麼只需要處理由可見變爲不可見狀態的fragment,由可見變爲不可見時,需要分發事件,處理中斷操作。
僞代碼:
public void onPause() {
super.onPause();
//這裏也就是說 當前可見的Fragment 需要分發中斷事件
if (當前可見 && getUserVisibleHint()){
//分發不可見事件
}
}
Fragment嵌套情況處理
bug出現情況,假設當前有三個tab,每個tab對應一個fragment,tab2對應的fragment裏嵌套了一層viewpager,當tab1加載時,tab2對應的fragment裏面嵌套的viewpager裏的第一個fragment也會執行onResume。這個情況是非常容易被忽略掉的,處理的邏輯很簡單,在分發事件時,首先判斷父Fragment是否可見,只有可見情況下才會繼續分發。
完整代碼
使用時,對應的Fragment繼承該父類,子類主動實現firstLoad、lazyLoad、interrupt 三個方法即可。如果有嵌套,嵌套的Fragment也繼承該父類即可
public abstract class LazyFragment extends Fragment {
protected View mRootView = null; //view複用
protected boolean isCreateView = false; //是否創建
protected boolean currentVisibleState = false; //當前可見狀態
protected boolean isFirstVisible = true; //是否第一次可見
protected abstract int setLayoutId();
//初始化view
protected abstract void initView(View view);
//第一次加載 留給子類實現
protected void firstLoad(){
}
//切換之後的懶加載 留給子類實現
protected void lazyLoad() {
}
//切換後中斷加載 留給子類實現
protected void interrupt() {
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//父view複用
if (mRootView == null) {
mRootView = inflater.inflate(setLayoutId(), container, false);
}
initView(mRootView);
//已經創建view
isCreateView = true;
//第一次加載
if (!isHidden() && getUserVisibleHint()) {
dispatchLazyLoadOrInterrupt(true);
}
return mRootView;
}
@Override
public void onResume() {
super.onResume();
/**
* 已經加載過的 fragment 會遇到的情況
* fragment 所在的 activity 跳轉到其他 activity
* 在 activity 返回時 需要加載
* 只加載可見 fragment
*/
if(!isFirstVisible){
if (!isHidden() && !currentVisibleState && getUserVisibleHint()){
dispatchLazyLoadOrInterrupt(true);
}
}
}
@Override
public void onPause() {
super.onPause();
//是否需要分發中斷事件
if (currentVisibleState && getUserVisibleHint()){
dispatchLazyLoadOrInterrupt(false);
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isCreateView) { // createview 之後纔可以加載數據
if (!currentVisibleState && isVisibleToUser) { //不可見 -> 可見
dispatchLazyLoadOrInterrupt(true);
} else if (currentVisibleState && !isVisibleToUser) { // 可見 -> 不可見
dispatchLazyLoadOrInterrupt(false);
}
}
}
/**
* 處理fragment切換時 懶加載 和 中斷
* 可見 -> 不可見 = 中斷操作,網絡請求等
* 不可見 -> 可見 = 懶加載
* @param isVisible
*/
private void dispatchLazyLoadOrInterrupt(boolean isVisible) {
// ParentFragment不可見 則不處理
// 處理fragment裏嵌套fragment的情況
if (isVisible && isParentInVisible()) {
return;
}
if (currentVisibleState == isVisible) {
return;
}
// save當前可見性
currentVisibleState = isVisible;
if (isVisible) {
/**
* fragment 第一次加載
* 主要處理這種情況:
* fragment第一次加載網絡請求的數據緩存到本地,之後加載去讀取本地
* 那麼,第一次網絡請求通過 firstLoad,之後在 lazyLoad 中讀取本都緩存
*/
if (isFirstVisible){
isFirstVisible = false;
firstLoad();
}
lazyLoad();
dispatchChildLazyLoadOrInterrupt(isVisible);
} else {
interrupt();
dispatchChildLazyLoadOrInterrupt(isVisible);
}
}
/**
* 當 fragment 中嵌套 fragment 時要處理 可見的childfragment
* @param isVisible
*/
public void dispatchChildLazyLoadOrInterrupt(boolean isVisible){
FragmentManager fm = getChildFragmentManager();
List<Fragment> fragmentList = fm.getFragments();
for (Fragment item : fragmentList){
if (item instanceof LazyFragment && !isHidden() && item.getUserVisibleHint()){
((LazyFragment) item).dispatchLazyLoadOrInterrupt(isVisible);
}
}
}
/**
* 當 fragment 通過 FragmentTransaction 的 show/hide 方法
* 改變 Fragment 可見性時,在這裏處理調度邏輯
* @param hidden
*/
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden){
dispatchLazyLoadOrInterrupt(false);
}else {
dispatchLazyLoadOrInterrupt(true);
}
}
/**
* ParentFragment 是不是不可見
*
* @return true -> 不可見 / false -> 可見
*/
private boolean isParentInVisible() {
Fragment parent = getParentFragment();
if (parent instanceof LazyFragment) {
LazyFragment temp = (LazyFragment) parent;
return !temp.getCurrentVisibleState();
}
return false;
}
/**
* 獲取 fragment 當前的可見性
* @return
*/
private boolean getCurrentVisibleState() {
return currentVisibleState;
}
}