1. 程式人生 > >Android 在 ViewPager 中使用 Fragment 的懶載入

Android 在 ViewPager 中使用 Fragment 的懶載入

ViewPager+Fragment的搭配在日常開發中也比較常見,可用於切換展示不同類別的頁面,我們日常所見的諮詢、購物、金融、社交等型別的APP都有機會用到這種控制元件組合.

如:

ViewPager控制元件有個特有的預載入機制,即預設情況下當前頁面左右兩側的1個頁面會被載入,以方便使用者滑動切換到相鄰的介面時,可以更加順暢的顯示出來。預載入讓使用者可以更快的看到接下來的內容,瀏覽起來連貫性更好,但是app在展示內容的同時還增加了額外的任務,這樣可能影響介面的流暢度,並且可能造成流量的浪費。

所以Fragment使用懶載入是非常有必要的。

試想那麼多的分類如果一下子都加載出來,真的是極大地消耗了系統資源。可能有人會說 ViewPager 有 viewPager.setOffscreenPageLimit() 

的方法,我們傳個 0 進去不就好了嗎?但是通過ViewPager方法setOffscreenPageLimit(int limit)的原始碼可以發現,ViewPager通過一定的邏輯判斷來確保至少會預載入左右兩側相鄰的1個頁面,也就是說無法通過簡單的配置做到懶載入的效果.

ViewPager方法setOffscreenPageLimit(int limit) 相關原始碼:

public void setOffscreenPageLimit(int limit) {
    if (limit < 1) {
        Log.w("ViewPager", "Requested offscreen page limit " + limit + " too small; defaulting to " + 1);
        limit = 1;
    }

    if (limit != this.mOffscreenPageLimit) {
        this.mOffscreenPageLimit = limit;
        this.populate();
    }

}

如何做到懶載入?

實現思路:

使用Fragment類自帶方法setUserVisibleHint()判斷當前fragment是否對使用者可見,根據回撥的isVisibleToUser引數來進行相關的邏輯判斷。重寫該方法,建立變數isVisible拿到是否可見標誌。

但是直接根據isVisible判斷就載入資料,可能onCreateView()方法並未執行完畢,此時就會出現NullPointerException空指標異常。所以就需要滿足控制元件初始化完成,使用者可見,才能載入資料。

由於ViewPager內會裝載多個Fragment,而這種懶載入機制對於各個Fragment屬於共同操作,因此適合將其抽取到BaseFragment中.

注意:

setUserVisibleHint(boolean isVisibleToUser)方法會多次回撥,而且可能會在onCreateView()方法執行完畢之前回調.如果isVisibleToUser==true,然後進行資料載入和控制元件資料填充,但是onCreateView()方法並未執行完畢,此時就會出現NullPointerException空指標異常.

基於以上原因,我們進行資料懶載入的時機需要滿足兩個條件

  1. onCreateView()方法執行完畢
  2. setUserVisibleHint(boolean isVisibleToUser)方法返回true

所以在BaseFragment中用兩個布林型標記來記錄這兩個條件的狀態.只有同時滿足了,才能載入資料

LazyloadFragment 程式碼:

public abstract class LazyloadFragment extends Fragment {

    protected View rootView;
    private boolean isInitView = false;
    private boolean isVisible = false;
    
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        rootView = inflater.inflate(setContentView(), container, false);
        init();
        isInitView = true;
        isCanLoadData();
        return rootView;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        
        //isVisibleToUser這個boolean值表示:該Fragment的UI 使用者是否可見,獲取該標誌記錄下來
        if (isVisibleToUser) {
            isVisible = true;
            isCanLoadData();
        } else {
            isVisible = false;
        }
    }

    private void isCanLoadData() {

        //所以條件是view初始化完成並且對使用者可見
        if (isInitView && isVisible) {
            lazyLoad();
            //防止重複載入資料
            isInitView = false;
            isVisible = false;
        }

    }
}

Fragment 程式碼:

public class PageFragment extends LazyloadFragment implements XRecyclerView.LoadingListener {
    private CommonAdapter<String> adapter;
    private ArrayList<String> datas = new ArrayList<>();
    private XRecyclerView recyclerView;
    private Handler handler = new Handler();

    @Override
    public int setContentView() {
        return R.layout.fragment_page;
    }


    @Override
    public void init() {
        recyclerView = rootView.findViewById(R.id.recyclerview);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        adapter = new CommonAdapter<String>(getActivity(),R.layout.item,datas) {
            @Override
            protected void convert(ViewHolder holder, String s, int position) {

            }
        };
        recyclerView.setAdapter(adapter);
        recyclerView.setPullRefreshEnabled(true);
        recyclerView.setLoadingListener(this);

    }

    @Override
    public void lazyLoad() {
        recyclerView.refresh();
    }

    @Override
    public void onRefresh() {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                recyclerView.refreshComplete();
                for(int i=0;i<10;i++){
                    datas.add("");
                }
                adapter.notifyDataSetChanged();
            }
        },500);
    }

    @Override
    public void onLoadMore() {

    }
}

MainActivity 程式碼:

public class MainActivity extends AppCompatActivity {
    private TabLayout tabLayout;
    private String[] topics = new String[]{"推薦","熱點","北京","視訊","社會","圖片"};
    private ViewPager viewPager;
    private ArrayList<Fragment> fragments = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_main);

        init();
    }

    private void init() {
        viewPager = (ViewPager) findViewById(R.id.viewpager);
        tabLayout = (TabLayout) findViewById(R.id.tablayout);
        viewPager.setOffscreenPageLimit(3);

        for(int i=0;i<topics.length;i++){
            tabLayout.addTab(tabLayout.newTab());
            fragments.add(new PageFragment());
        }
        viewPager.setAdapter(new FmPagerAdapter(fragments,getSupportFragmentManager()));
        tabLayout.setupWithViewPager(viewPager);

        for (int j = 0; j < topics.length; j++) {
            tabLayout.getTabAt(j).setText(topics[j]);
        }
    }
}

大坑:

大家千篇一律地說用setUserVisibleHint()方法就可以了,但是沒有說這個問題。是不是用了Lazyloadfragment不載入資料了?因為你用的是Viewpager用的是PagerAdapter,用pageradapter,打斷點除錯,根本就沒有呼叫setUserVisibleHint(),所以isVisible還是false,不執行lazyload方法。需要用FragmentPagerAdapter顯示呼叫setUserVisibleHint()。