Android 在 ViewPager 中使用 Fragment 的懶載入
ViewPager+Fragment的搭配在日常開發中也比較常見,可用於切換展示不同類別的頁面,我們日常所見的諮詢、購物、金融、社交等型別的APP都有機會用到這種控制元件組合.
如:
ViewPager控制元件有個特有的預載入機制,即預設情況下當前頁面左右兩側的1個頁面會被載入,以方便使用者滑動切換到相鄰的介面時,可以更加順暢的顯示出來。預載入讓使用者可以更快的看到接下來的內容,瀏覽起來連貫性更好,但是app在展示內容的同時還增加了額外的任務,這樣可能影響介面的流暢度,並且可能造成流量的浪費。
所以Fragment使用懶載入是非常有必要的。
試想那麼多的分類如果一下子都加載出來,真的是極大地消耗了系統資源。可能有人會說 ViewPager 有 viewPager.setOffscreenPageLimit()
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空指標異常.
基於以上原因,我們進行資料懶載入的時機需要滿足兩個條件
onCreateView()
方法執行完畢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()。