Android中ViewPager+Fragment取消(禁止)預加載延遲加載(懶加載)問題解決方案

轉載請註明出處:http://blog.csdn.net/linglongxin24/article/details/53205878
本文出自【DylanAndroid的博客】


Android中ViewPager+Fragment取消(禁止)預加載延遲加載(懶加載)問題解決方案

在Android中我們經常會用到ViewPager+Fragment組合。然而,有一個很讓人頭疼的問題就是,我們去加載數據的時候由於ViewPager的內部機制所限制,所以它會默認至少預加載一個。這讓人很鬱悶,所以,我就想到要封裝一個Fragment來解決這個問題。
這裏還解決一個問題就是在Android酷炫歡迎頁播放視頻,仿螞蜂窩自由行和慕課網
這裏感謝有一位網友提出了bug,就是在播放視頻的時候如果滑動到第二頁和第三頁,第一頁的視頻還在播放,這是個讓人很頭疼的問題,在這裏也完美解決。

1.問題初探

文章開始已經說過ViewPager的預加載機制。那麼,我們可不可以設置ViewPager的預加載爲0,不就解決問題了嗎?

        vp.setOffscreenPageLimit(0);

經過測試發現,根本不是這麼回事,爲什麼呢?我們來看看Viewpager的setOffscreenPageLimit()方法的源碼

   private static final int DEFAULT_OFFSCREEN_PAGES = 1;
    /**
     * Set the number of pages that should be retained to either side of the
     * current page in the view hierarchy in an idle state. Pages beyond this
     * limit will be recreated from the adapter when needed.
     *
     * <p>This is offered as an optimization. If you know in advance the number
     * of pages you will need to support or have lazy-loading mechanisms in place
     * on your pages, tweaking this setting can have benefits in perceived smoothness
     * of paging animations and interaction. If you have a small number of pages (3-4)
     * that you can keep active all at once, less time will be spent in layout for
     * newly created view subtrees as the user pages back and forth.</p>
     *
     * <p>You should keep this limit low, especially if your pages have complex layouts.
     * This setting defaults to 1.</p>
     *
     * @param limit How many pages will be kept offscreen in an idle state.
     */
    public void setOffscreenPageLimit(int limit) {
        if (limit < DEFAULT_OFFSCREEN_PAGES) {
            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                    + DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }
        if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            populate();
        }
    }

我們發現,即使你設置爲0,那麼還是會在裏面判斷後設爲默認值1。所以這個方法是行不通的。

2.問題再探

我們發現Fragment中有一個setUserVisibleHint(boolean isVisibleToUser)方法,這個方法就是告訴用戶,UI對用戶是否可見,那麼我們在這裏去加載數據會怎麼樣呢?
日誌
這又是爲什麼呢?
因爲ViewPager會加載好多Fragment,爲了節省內容等會在Fragment不可見的某個時候調用onDestroyView()將用戶界面銷燬掉但是Fragment的實例還在,所以可能第一次加載沒有問題,
但是再次回到第一個Fragment再去加載的時候就會出現UI對用戶可見但是視圖還沒有初始化。

3.最終解決方案

package cn.bluemobi.dylan.viewpagerfragmentlazyload;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

/**
 * Fragment預加載問題的解決方案:
 * 1.可以懶加載的Fragment
 * 2.切換到其他頁面時停止加載數據(可選)
 * Created by yuandl on 2016-11-17.
 * blog :http://blog.csdn.net/linglongxin24/article/details/53205878
 */

public abstract class LazyLoadFragment extends Fragment {
    /**
     * 視圖是否已經初初始化
     */
    protected boolean isInit = false;
    protected boolean isLoad = false;
    protected final String TAG = "LazyLoadFragment";
    private View view;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        view = inflater.inflate(setContentView(), container, false);
        isInit = true;
        /**初始化的時候去加載數據**/
        isCanLoadData();
        return view;
    }

    /**
     * 視圖是否已經對用戶可見,系統的方法
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        isCanLoadData();
    }

    /**
     * 是否可以加載數據
     * 可以加載數據的條件:
     * 1.視圖已經初始化
     * 2.視圖對用戶可見
     */
    private void isCanLoadData() {
        if (!isInit) {
            return;
        }

        if (getUserVisibleHint()) {
            lazyLoad();
            isLoad = true;
        } else {
            if (isLoad) {
                stopLoad();
            }
        }
    }

    /**
     * 視圖銷燬的時候講Fragment是否初始化的狀態變爲false
     */
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        isInit = false;
        isLoad = false;

    }

    protected void showToast(String message) {
        if (!TextUtils.isEmpty(message)) {
            Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
        }

    }

    /**
     * 設置Fragment要顯示的佈局
     *
     * @return 佈局的layoutId
     */
    protected abstract int setContentView();

    /**
     * 獲取設置的佈局
     *
     * @return
     */
    protected View getContentView() {
        return view;
    }

    /**
     * 找出對應的控件
     *
     * @param id
     * @param <T>
     * @return
     */
    protected <T extends View> T findViewById(int id) {

        return (T) getContentView().findViewById(id);
    }

    /**
     * 當視圖初始化並且對用戶可見的時候去真正的加載數據
     */
    protected abstract void lazyLoad();

    /**
     * 當視圖已經對用戶不可見並且加載過數據,如果需要在切換到其他頁面時停止加載數據,可以覆寫此方法
     */
    protected void stopLoad() {
    }
}

}

4.用法

LazyLoadFragment是一個抽象類,可以作爲BaseFragment,繼承它。

  • (1).用setContentView()方法去加載要顯示的佈局

  • (2).lazyLoad()方法去加載數據

  • (3).stopLoad()方法可選,當視圖已經對用戶不可見並且加載過數據,如果需要在切換到其他頁面時停止加載數據,可以覆寫此方法

    package cn.bluemobi.dylan.viewpagerfragmentlazyload;
    
    import android.util.Log;
    
    /**
    * Created by yuandl on 2016-11-17.
    */
    
    public class Fragment1 extends LazyLoadFragment {
     @Override
     public int setContentView() {
         return R.layout.fm_layout1;
     }
    
     @Override
     protected void lazyLoad() {
         String message = "Fragment1" + (isInit ? "已經初始並已經顯示給用戶可以加載數據" : "沒有初始化不能加載數據")+">>>>>>>>>>>>>>>>>>>";
         showToast(message);
         Log.d(TAG, message);
     }
    
     @Override
     protected void stopLoad() {
         Log.d(TAG, "Fragment1" + "已經對用戶不可見,可以停止加載數據");
     }
    }
    
    

5.看效果界面

界面
Log

6.GitHub

發佈了134 篇原創文章 · 獲贊 73 · 訪問量 63萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章