ViewPager2 延迟加载数据

ViewPager 实现预加载的方案

背景

现在项目采用的viewpager + Tablayout的联合使用, 为了优化页面加载流畅性的问题,希望采取的懒加载策略,但是因为使用的是viewpager需要通过Fragment的setUserVisibleHint的回调来得知当前Fragment是否可见。

可见下方示例代码

| activity代码

  1. final ViewPager viewpager = findViewById(R.id.vp_content);
  2. final List<BlankFragmentV1> fragments = new ArrayList<>();
  3. for (int i = 0; i < 20; i++) {
  4. fragments.add(new BlankFragmentV1(i));
  5. }
  6. viewpager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
  7. @Nullable
  8. @Override
  9. public CharSequence getPageTitle(int position) {
  10. return String.valueOf(position);
  11. }
  12. @NonNull
  13. @Override
  14. public Fragment getItem(int position) {
  15. return fragments.get(position);
  16. }
  17. @Override
  18. public int getCount() {
  19. return fragments.size();
  20. }
  21. });
  22. tabLayout.setupWithViewPager(viewpager);
  23. // 至少为1, 预预载的Fragment一定会走onResume方法,ViewPager对offscreenPageLimit的设置并不友好
  24. // 只能依赖setUserVisibleHint判断当前Fragment是否处于可见状态
  25. viewpager.setOffscreenPageLimit(1);

| Fragment代码

  1. public class BlankFragmentV1 extends Fragment {
  2. // 数据是否加载
  3. private boolean isDataLoad;
  4. // 视图是否已构建
  5. private boolean isPrepared;
  6. private static final String TAG = "BlankFragmentV1";
  7. private int position;
  8. public BlankFragmentV1(int position) {
  9. this.position = position;
  10. }
  11. @Nullable
  12. @Override
  13. public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  14. Log.i(TAG, "onCreateView: " + position);
  15. loadData();
  16. isDataLoad = true;
  17. return super.onCreateView(inflater, container, savedInstanceState);
  18. }
  19. @Override
  20. public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
  21. super.onViewCreated(view, savedInstanceState);
  22. Log.i(TAG, "onViewCreated: " + position);
  23. }
  24. @Override
  25. public void onStart() {
  26. super.onStart();
  27. Log.i(TAG, "onStart: " + position);
  28. }
  29. @Override
  30. public void onResume() {
  31. super.onResume();
  32. Log.i(TAG, "onResume: " + position);
  33. }
  34. @Override
  35. public void onDestroy() {
  36. super.onDestroy();
  37. Log.i(TAG, "onDestroy: " + position);
  38. }
  39. @Override
  40. public void setUserVisibleHint(boolean isVisibleToUser) {
  41. super.setUserVisibleHint(isVisibleToUser);
  42. if (isVisibleToUser) {
  43. if (!isDataLoad && isPrepared) {
  44. loadData();
  45. isDataLoad = true;
  46. }
  47. }
  48. Log.i(TAG, "setUserVisibleHint: " + position + " " + isVisibleToUser);
  49. }
  50. @Override
  51. public void onDestroyView() {
  52. super.onDestroyView();
  53. isDataLoad = false;
  54. isPrepared = false;
  55. }
  56. public void loadData() {
  57. Log.i(TAG, "lazy load" + position);
  58. }
  59. }

上述代码示例中的问题就是viewPager offscreenPageLimit的设置只能控制离屏加载的fragment个数,但其实预加载的fragment还是会走onResume的生命周期,所以没法通过onResume来判断Fragment是否可见,只能通过setUserVisibleHint来判断是否View可见。但也不能完全依赖setUserVisibleHint来加载数据,因为默认的第一个Fragment调用setUserVisibleHint时,因为其onViewCreated还没走,所以View还没创建。

总上所述:ViewPager来实现延迟加载数据并不方便,限制很多,实现起来也不优雅,且setUserVisibleHint这个方法本就已经是要废弃的方法,不应该继续使用了

ViewPager2 实现预加载的方案

ViewPager2可以理解为google后来推出的ViewPager增强版,增加了很多新的功能,如:

  • 垂直方向支持
  • 支持diffUtil
  • 支持RTL
  • ...

| activity代码

  1. final List<BlankFragmentV2> fragments = new ArrayList<>();
  2. for (int i = 0; i < 20; i++) {
  3. fragments.add(new BlankFragmentV2(i));
  4. }
  5. viewpager.setAdapter(new FragmentStateAdapter(this) {
  6. @NonNull
  7. @Override
  8. public Fragment createFragment(int position) {
  9. BlankFragmentV2 fragment = fragments.get(position);
  10. return fragment;
  11. }
  12. @Override
  13. public int getItemCount() {
  14. return fragments.size();
  15. }
  16. });
  17. viewpager.setOffscreenPageLimit(fragments.size());
  18. new TabLayoutMediator(tabLayout, viewpager, true, (tab, position) -> {
  19. tab.setText("fragment"+position);
  20. tab.setIcon(R.drawable.icon);
  21. }).attach();
  22. // 禁止用户左右滑动
  23. // viewpager.setUserInputEnabled(false);
  24. viewpager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
  25. @Override
  26. public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
  27. super.onPageScrolled(position, positionOffset, positionOffsetPixels);
  28. }
  29. @Override
  30. public void onPageSelected(int position) {
  31. super.onPageSelected(position);
  32. Log.i(TAG, "onPageSelected: " + Objects.requireNonNull(tabLayout.getTabAt(position)).getText());
  33. }
  34. @Override
  35. public void onPageScrollStateChanged(int state) {
  36. super.onPageScrollStateChanged(state);
  37. }
  38. });
  39. viewpager.setOffscreenPageLimit(1);

| Fragment代码

  1. private boolean isFirstLoad = true;
  2. private static final String TAG = "BlankFragmentV2";
  3. private int position;
  4. public BlankFragmentV2(int position) {
  5. this.position = position;
  6. }
  7. @Nullable
  8. @Override
  9. public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  10. Log.i(TAG, "onCreateView: " + position);
  11. return super.onCreateView(inflater, container, savedInstanceState);
  12. }
  13. @Override
  14. public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
  15. super.onViewCreated(view, savedInstanceState);
  16. Log.i(TAG, "onViewCreated: " + position);
  17. }
  18. @Override
  19. public void onStart() {
  20. super.onStart();
  21. Log.i(TAG, "onStart: " + position);
  22. }
  23. @Override
  24. public void onResume() {
  25. super.onResume();
  26. Log.i(TAG, "onResume: " + position);
  27. if (isFirstLoad) {
  28. isFirstLoad = false;
  29. loadData();
  30. }
  31. }
  32. @Override
  33. public void onDestroy() {
  34. super.onDestroy();
  35. Log.i(TAG, "onDestroy: " + position);
  36. }
  37. @Override
  38. public void setUserVisibleHint(boolean isVisibleToUser) {
  39. super.setUserVisibleHint(isVisibleToUser);
  40. Log.i(TAG, "setUserVisibleHint: " + position + " " + isVisibleToUser);
  41. }
  42. private void loadData() {
  43. Log.i(TAG, "lazy load" + position);
  44. }

上述代码中可以看到通过使用ViewPager2我们完全可以依赖于OnResume来进行进行延迟加载数据,相比于ViewPager而言简易得多,而setOffscreenPageLimit中设置的离屏加载的值会帮助我们需要预载多少个相离的界面和需要销毁的界面,如下图所示

总结

ViewPager2在各方面的能力都会比ViewPager强,其配套的FragmentStateAdapter在遇到预加载时,只会创建Fragment对象,不会把Fragment真正地加入布局中,自带懒加载效果。

PS:需要注意是FragmentStateAdapter不会一直保持Fragment实例,在被destroy后,需要做好Fragment重建后回复数据的准备,这点可以结合ViewModel来进行配合使用。

ViewPager2 学习的更多相关文章

  1. 学习Android Jetpack? 入门教程和进阶实战这里全都有!

    前言 2018年谷歌I/O,Jetpack横空出世,官方介绍如下: Jetpack 是一套库.工具和指南,可帮助开发者更轻松地编写优质应用.这些组件可帮助您遵循最佳做法.让您摆脱编写样板代码的工作并简 ...

  2. ViewPager2 使用说明书

    ViewPager2 使用说明书 零.Demo 项目源码 演示 apk 如果对你有用,希望能给个 star,谢谢. 一.功能 官方关于使用 ViewPager2 创建滑动视图的说明: Swipe vi ...

  3. 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代

    2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...

  4. Angular2学习笔记(1)

    Angular2学习笔记(1) 1. 写在前面 之前基于Electron写过一个Markdown编辑器.就其功能而言,主要功能已经实现,一些小的不影响使用的功能由于时间关系还没有完成:但就代码而言,之 ...

  5. ABP入门系列(1)——学习Abp框架之实操演练

    作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...

  6. 消息队列——RabbitMQ学习笔记

    消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...

  7. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  8. Unity3d学习 制作地形

    这周学习了如何在unity中制作地形,就是在一个Terrain的对象上盖几座小山,在山底种几棵树,那就讲一下如何完成上述内容. 1.在新键得项目的游戏的Hierarchy目录中新键一个Terrain对 ...

  9. 《Django By Example》第四章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:祝大家新年快乐,这次带来<D ...

随机推荐

  1. go模板-代码生成器

    能用程序去做的事,就不要用手,编写自己的代码生成器就是用来解放你的双手,替你做一些重复性的工作. 上篇帖子写了模板的基础 go模板详说 ,有了基础就要做点什么东西,把所学到的东西应用起来才能更好的进步 ...

  2. 解决docker创建的elasticsearch-head容器不能连接elasticsearch等问题

    在使用docker创建elasticsearch-head容器去连接elasticsearch的时候,容易出两个问题 1.不能连接elasticsearch 修改elasticsearch.yml文件 ...

  3. 剑指Offer之旋转数组的最小数字

    题目描述 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素.例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转 ...

  4. 整理总结数据库常用sql语句,建议收藏,忘记了可以来看一下

    第一节课:sql语言介绍(参照PPT)及基本查询sql学习 1.数据库表的介绍 emp表:员工表 dept表:部门表 salgrady:薪资水平表 Balance: 2.基本的查询语句: 知识点: s ...

  5. 【数据结构的JavaScript版实现】data-struct-js的npm包初版作成

    [数据结构的JavaScript版实现]data-struct-js的npm包初版作成 码路工人 CoderMonkey [数据结构的JavaScript版实现] 拖了这么久,终于趁着春节假期把初版( ...

  6. ASP.NET Core Blazor Webassembly 之 数据绑定

    上一次我们学习了Blazor组件相关的知识(Asp.net Core Blazor Webassembly - 组件).这次继续学习Blazor的数据绑定相关的知识.当代前端框架都离不开数据绑定技术. ...

  7. 网络编程TCP/IP详解

    网络编程TCP/IP详解 1. 网络通信 中继器:信号放大器 集线器(hub):是中继器的一种形式,区别在于集线器能够提供多端口服务,多口中继器,每个数据包的发送都是以广播的形式进行的,容易阻塞网络. ...

  8. LayUI laydate日期选择器自定义 快捷选中今天、昨天 、本周、本月等等

    1. 引入laydata插件 下载 https://blog-static.cnblogs.com/files/zhangning187/laydate.js laydate.js 替换laydate ...

  9. Java实现蓝桥杯 历届试题 k倍区间

    历届试题 k倍区间 时间限制:2.0s 内存限制:256.0MB 问题描述 给定一个长度为N的数列,A1, A2, - AN,如果其中一段连续的子序列Ai, Ai+1, - Aj(i <= j) ...

  10. Java实现P2102 -- 正整数序列

    P2102 – 正整数序列 给定正整数n, 你的任务是用最少的操作次数把序列1,2,-,n中的所有数都变成0.每次操作可从序列中选择一个或多个整数, 同时减去一个相同的正整数.比如,1,2,3可以把2 ...