ViewPager2 学习
ViewPager2 延迟加载数据
ViewPager 实现预加载的方案
背景
现在项目采用的viewpager + Tablayout的联合使用, 为了优化页面加载流畅性的问题,希望采取的懒加载策略,但是因为使用的是viewpager需要通过Fragment的setUserVisibleHint的回调来得知当前Fragment是否可见。
可见下方示例代码
| activity代码
final ViewPager viewpager = findViewById(R.id.vp_content);
final List<BlankFragmentV1> fragments = new ArrayList<>();
for (int i = 0; i < 20; i++) {
fragments.add(new BlankFragmentV1(i));
}
viewpager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return String.valueOf(position);
}
@NonNull
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
});
tabLayout.setupWithViewPager(viewpager);
// 至少为1, 预预载的Fragment一定会走onResume方法,ViewPager对offscreenPageLimit的设置并不友好
// 只能依赖setUserVisibleHint判断当前Fragment是否处于可见状态
viewpager.setOffscreenPageLimit(1);
| Fragment代码
public class BlankFragmentV1 extends Fragment {
// 数据是否加载
private boolean isDataLoad;
// 视图是否已构建
private boolean isPrepared;
private static final String TAG = "BlankFragmentV1";
private int position;
public BlankFragmentV1(int position) {
this.position = position;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.i(TAG, "onCreateView: " + position);
loadData();
isDataLoad = true;
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.i(TAG, "onViewCreated: " + position);
}
@Override
public void onStart() {
super.onStart();
Log.i(TAG, "onStart: " + position);
}
@Override
public void onResume() {
super.onResume();
Log.i(TAG, "onResume: " + position);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: " + position);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
if (!isDataLoad && isPrepared) {
loadData();
isDataLoad = true;
}
}
Log.i(TAG, "setUserVisibleHint: " + position + " " + isVisibleToUser);
}
@Override
public void onDestroyView() {
super.onDestroyView();
isDataLoad = false;
isPrepared = false;
}
public void loadData() {
Log.i(TAG, "lazy load" + position);
}
}

上述代码示例中的问题就是viewPager offscreenPageLimit的设置只能控制离屏加载的fragment个数,但其实预加载的fragment还是会走onResume的生命周期,所以没法通过onResume来判断Fragment是否可见,只能通过setUserVisibleHint来判断是否View可见。但也不能完全依赖setUserVisibleHint来加载数据,因为默认的第一个Fragment调用setUserVisibleHint时,因为其onViewCreated还没走,所以View还没创建。
总上所述:ViewPager来实现延迟加载数据并不方便,限制很多,实现起来也不优雅,且setUserVisibleHint这个方法本就已经是要废弃的方法,不应该继续使用了
ViewPager2 实现预加载的方案
ViewPager2可以理解为google后来推出的ViewPager增强版,增加了很多新的功能,如:
- 垂直方向支持
- 支持diffUtil
- 支持RTL
- ...
| activity代码
final List<BlankFragmentV2> fragments = new ArrayList<>();
for (int i = 0; i < 20; i++) {
fragments.add(new BlankFragmentV2(i));
}
viewpager.setAdapter(new FragmentStateAdapter(this) {
@NonNull
@Override
public Fragment createFragment(int position) {
BlankFragmentV2 fragment = fragments.get(position);
return fragment;
}
@Override
public int getItemCount() {
return fragments.size();
}
});
viewpager.setOffscreenPageLimit(fragments.size());
new TabLayoutMediator(tabLayout, viewpager, true, (tab, position) -> {
tab.setText("fragment"+position);
tab.setIcon(R.drawable.icon);
}).attach();
// 禁止用户左右滑动
// viewpager.setUserInputEnabled(false);
viewpager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
Log.i(TAG, "onPageSelected: " + Objects.requireNonNull(tabLayout.getTabAt(position)).getText());
}
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
});
viewpager.setOffscreenPageLimit(1);
| Fragment代码
private boolean isFirstLoad = true;
private static final String TAG = "BlankFragmentV2";
private int position;
public BlankFragmentV2(int position) {
this.position = position;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.i(TAG, "onCreateView: " + position);
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.i(TAG, "onViewCreated: " + position);
}
@Override
public void onStart() {
super.onStart();
Log.i(TAG, "onStart: " + position);
}
@Override
public void onResume() {
super.onResume();
Log.i(TAG, "onResume: " + position);
if (isFirstLoad) {
isFirstLoad = false;
loadData();
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: " + position);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.i(TAG, "setUserVisibleHint: " + position + " " + isVisibleToUser);
}
private void loadData() {
Log.i(TAG, "lazy load" + position);
}

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

总结
ViewPager2在各方面的能力都会比ViewPager强,其配套的FragmentStateAdapter在遇到预加载时,只会创建Fragment对象,不会把Fragment真正地加入布局中,自带懒加载效果。
PS:需要注意是FragmentStateAdapter不会一直保持Fragment实例,在被destroy后,需要做好Fragment重建后回复数据的准备,这点可以结合ViewModel来进行配合使用。
ViewPager2 学习的更多相关文章
- 学习Android Jetpack? 入门教程和进阶实战这里全都有!
前言 2018年谷歌I/O,Jetpack横空出世,官方介绍如下: Jetpack 是一套库.工具和指南,可帮助开发者更轻松地编写优质应用.这些组件可帮助您遵循最佳做法.让您摆脱编写样板代码的工作并简 ...
- ViewPager2 使用说明书
ViewPager2 使用说明书 零.Demo 项目源码 演示 apk 如果对你有用,希望能给个 star,谢谢. 一.功能 官方关于使用 ViewPager2 创建滑动视图的说明: Swipe vi ...
- 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代
2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...
- Angular2学习笔记(1)
Angular2学习笔记(1) 1. 写在前面 之前基于Electron写过一个Markdown编辑器.就其功能而言,主要功能已经实现,一些小的不影响使用的功能由于时间关系还没有完成:但就代码而言,之 ...
- ABP入门系列(1)——学习Abp框架之实操演练
作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...
- 消息队列——RabbitMQ学习笔记
消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- Unity3d学习 制作地形
这周学习了如何在unity中制作地形,就是在一个Terrain的对象上盖几座小山,在山底种几棵树,那就讲一下如何完成上述内容. 1.在新键得项目的游戏的Hierarchy目录中新键一个Terrain对 ...
- 《Django By Example》第四章 中文 翻译 (个人学习,渣翻)
书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:祝大家新年快乐,这次带来<D ...
随机推荐
- [Objective-C] 019_UIVIewController
UIViewController是iOS程序中的一个重要组成部分,对应MVC设计模式的C,它管理着程序中的众多视图,何时加载视图,视图何时消,界面的旋转等. 1.UIViewController 创建 ...
- unix 密码破解,zip破解总结
unix /etc/passwd 破解,假设的前两位是salt import crypt #数据比较 def password_crak(pass_word): salt = pass_word[0: ...
- MySQL 可重复读,差点就我背上了一个 P0 事故!
小黑黑的碎碎念 哎,最近有点忙,备考复习不利,明天还要搬家,好难啊!! 本想着这周鸽了,但是想想还是不行,爬起来,更新一下,周更可不能断.偷懒一下,修改一下之前的一篇历史文章,重新发布一下. P0 事 ...
- 小谢第8问:ui框架的css样式如何更改
目前有三种方法, 1.使用scss,增加样式覆盖,但是此种方法要求css的className需要与框架内的元素相一致,因此修改时候需要特别注意,一个父级的不同就可能修改失败 2.deep穿透,这种方法 ...
- Chisel3 - Tutorial - Adder
https://mp.weixin.qq.com/s/SEcVjGRL1YloGlEPSoHr3A 位数为参数的加法器.通过FullAdder级联实现. 参考链接: https://githu ...
- Docker 容器优雅终止方案
原文链接:Docker 容器优雅终止方案 作为一名系统重启工程师(SRE),你可能经常需要重启容器,毕竟 Kubernetes 的优势就是快速弹性伸缩和故障恢复,遇到问题先重启容器再说,几秒钟即可恢复 ...
- 面试题:我们重写一个对象的时候为什么要同时重写hashcode()和equals()方法
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 在创建的类不重写hashCode()和equals() 方法时,默认使用 java 提供的 java.l ...
- Java实现 LeetCode 86 分割链表
86. 分隔链表 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前. 你应当保留两个分区中每个节点的初始相对位置. 示例: 输入: head = 1 ...
- PAT 有理数四则运算
本题要求编写程序,计算 2 个有理数的和.差.积.商. 输入格式: 输入在一行中按照 a1/b1 a2/b2 的格式给出两个分数形式的有理数,其中分子和分母全是整型范围内的整数,负号只可能出现在分子前 ...
- 对LinkedList源码的一些个人理解
由于转行的原因,最近打算开始好好学习,昨天看到了部分的LinkedList源码,并且看了一点数据结构的视频,现总结部分自己的心得体会,以供后期给现在的自己拍砖~ 双向链表每一个元素都有数据本身加指向前 ...