Jetpack系列之 Paging 详解
在此之前,我一直对Jetpack的Paging感觉到很迷茫,单单一个分页为啥 Android 官方会出一个组件? 在我们眼中,分页不就是添加两个参数pageSize和pageIndex 么?这么简单逻辑Android官方能耍得起什么样的波浪么?带着这个问题,阅读了一些文档,加上自己的理解,然后有了这篇文章。
基本原理
Paging目前来说,是需要和RecyclerView配合使用的,毕竟Android 目前展示列表数据,差不多就是RecyclerView了。首先简单介绍一下Paging的工作原理,
完成以上步骤,涉及到几个类分别为:
RecyclerView 负责列表展示;
PagedListAdapter RecyclerView的适配器 同时负责通知PagedList何时加载更多数据
PagedList 控制分页加载的逻辑,比如加载的数量,每页的大小,是否显示 item PlaceHolder等等;
DataSource 执行数据获取的逻辑,它本身并不存储数据,获取到数据丢给PagedList存储。
以上的四个核心类理解完成之后,大致流程如下:
RecyclerView对应的Adapter为PagedListAdapter,通知PagedList需要获取数据,此时PagedList通过DataSource真正执行获取数据的逻辑,返回的数据给PagedList,然后PagedList将数据传递给PagedListAdapter,最后在RecyclerView中显示。
Paging支持的三种分页方式
PositionalDataSource
支持从任意位置开始,取多少条数据的方式,类似SQL中 " XX > id limit 100", 又类似 "start=100&count=20" , 意味着从第100的位置开始,向后取20条数据。
PageKeyedDataSource
这是我们最熟悉的模式,即"pageIndex=1&pageSize=20"的模式。使用以”页“的方式请求数据。
ItemKeyedDataSource
”maxId=nextId&count=200“模式,此次请求依赖上一次的的数据。这种请求方式一般在社交评论中用得比较多,只有这一次请求成功了,下一次请求才能依赖本次的某种参数继续请求。
当然,这只是Google设计的三种用得比较多的请求方式,最终需要你选择的是你的服务器适合哪种方式,然后你再去采用这种方式。
分页实践
首先我们定义接口:
按照上面三种类型分别定义三种接口
获取用户的列表,从start开始,获取count个用户信息。
首先我们定义接口:
按照上面三种类型分别定义三种接口
获取用户的列表,从start开始,获取count个用户信息。
http://api.com/userList?start=0&count=20
获取用户的列表,从pageIndex页开始开始,获取pageSize个用户信息。
http://api.com/userList2?pageIndex=0&pageSize=20
获取用户的列表,从nextId起始点开始,获取pageCount个用户信息
http://api.com/userList3?nextId=0&pageCount=20
大概的返回结果我们也可以定义一下:
{"code":200,"msg": "success","data": {"total":"100","userList" : [{ "userId":1,"userName":"tom","userAge": 20,"userAvatar" : "http://api.com/user/avatar.png"},{ "userId":2,"userName":"Jerry","userAge": 18,"userAvatar" : "http://api.com/user/avatar2.png"}]}
}
针对三种API,Paging分别提供了三个抽象类:PositionalDataSource
、PageKeyedDataSource
、ItemKeyedDataSource
满足以上接口分类。我们一个一个来。先看一下我们的项目目录结构:
使用到的 dependences为:
implementation 'com.squareup.retrofit2:retrofit:2.7.2'implementation 'com.squareup.retrofit2:converter-gson:2.7.2'implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'implementation 'androidx.paging:paging-runtime:2.1.2'implementation 'androidx.recyclerview:recyclerview:1.1.0'
首先定义Model类:
public class UserInfo {public int userId;public String userName;public String userAvatar;
}public class UserModel {public int code;public String msg;public UserList data;public static class UserList{public int total;public List<UserInfo> userList;}
}
然后定义我们的Api类:
public interface Api {@GET("/userList/")Call<UserModel> getUserListByPositional(@Query("start") int start,@Query("count") int count);@GET("/userList2/")Call<UserModel> getUserListByPageSize(@Query("pageIndex") int pageIndex,@Query("pageSize") int pageSize);@GET("/userList3/")Call<UserModel> getUserListByItemPaged(@Query("nextId") int nextId,@Query("pageCount") int pageSize);}
然后定义我们的 RetrofitClient类,都比较简单,贴一下代码:
public class RetrofitClient {private static final String BASE_URL = "https://your.host.url";private static RetrofitClient instance ;private Retrofit mRetrofit ;private RetrofitClient() {mRetrofit = new Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).client(new OkHttpClient()).build();}public synchronized static RetrofitClient getInstance() {if(null == instance) {instance = new RetrofitClient();}return instance;}public Api getApi() {return mRetrofit.create(Api.class);}
}
PositionalUserDataSource
针对于 PositionalDataSource
,来看我们的代码:
public class PositionalUserDataSource extends PositionalDataSource<UserInfo> {public static final int PAGE_SIZE = 20 ;/*** 首次加载数据*/@Overridepublic void loadInitial(@NonNull LoadInitialParams params,@NonNull final LoadInitialCallback<UserInfo> callback) {final int startPosition = 0;RetrofitClient.getInstance().getApi().getUserListByPositional(startPosition,PAGE_SIZE).enqueue(new Callback<UserModel>() {@Overridepublic void onResponse(@NonNull Call<UserModel> call,@NonNull Response<UserModel> response) {if(response.isSuccessful()) {UserModel body = response.body();if(null != body) {callback.onResult(body.data.userList,startPosition, body.data.total);}}}@Overridepublic void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {}});}/**** 第 N 页加载数据** @param params* @param callback*/@Overridepublic void loadRange(@NonNull LoadRangeParams params,@NonNull final LoadRangeCallback<UserInfo> callback) {RetrofitClient.getInstance().getApi().getUserListByPositional(params.startPosition,PAGE_SIZE).enqueue(new Callback<UserModel>() {@Overridepublic void onResponse(@NonNull Call<UserModel> call, @NonNull Response<UserModel> response) {if(response.isSuccessful() && null != response.body()){callback.onResult(response.body().data.userList);}}@Overridepublic void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {}});}
}
有两个抽象方法需要我们重写:loadInitial
、loadRange
方法:
其中 loadInitial
代表加载第一页的方法,但是需要注意的是 LoadInitialCallback.onResult
方法:
是要告之请求的List的总长度,如果我们在 PagedList.Config中设置了 setEnablePlaceHolders() 方法为true,那么此处我们就应该设置List的totalCount,否则我们大程序就会报错。
loadRange 方法可以理解为加载下一页的动作,数据加载完成之后,仍然在 LoadInitialCallback.onResult中显示结果。
PagedKeyUserDataSource
同理,PagedKeyUserDataSource继承自抽象类PageKeyedDataSource:
public class PagedKeyUserDataSource extends PageKeyedDataSource<Integer, UserInfo> {public static final int FIRST_PAGE = 1 ;public static final int PAGE_SIZE = 20 ;/*** 加载第一页数据** @param params* @param callback*/@Overridepublic void loadInitial(@NonNull LoadInitialParams<Integer> params,@NonNull final LoadInitialCallback<Integer, UserInfo> callback) {RetrofitClient.getInstance().getApi().getUserListByPageSize(FIRST_PAGE,FIRST_PAGE).enqueue(new Callback<UserModel>() {@Overridepublic void onResponse(@NonNull Call<UserModel> call,@NonNull Response<UserModel> response) {if(response.isSuccessful() && null != response.body()){callback.onResult(response.body().data.userList,null,FIRST_PAGE + 1);}}@Overridepublic void onFailure(Call<UserModel> call, Throwable t) {}});}/*** 加载下一页的数据** @param params* @param callback*/@Overridepublic void loadAfter(@NonNull final LoadParams<Integer> params,@NonNull final LoadCallback<Integer, UserInfo> callback) {RetrofitClient.getInstance().getApi().getUserListByPageSize(params.key, PAGE_SIZE).enqueue(new Callback<UserModel>() {@Overridepublic void onResponse(@NonNull Call<UserModel> call, @NonNull Response<UserModel> response) {if(response.isSuccessful() && null != response.body()){UserModel.UserList userList = response.body().data;boolean hasMoreData = userList != null && userList.userList.size() >= PAGE_SIZE;callback.onResult(userList.userList, hasMoreData ? params.key + 1 : null);}}@Overridepublic void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {}});}@Overridepublic void loadBefore(@NonNull LoadParams<Integer> params,@NonNull LoadCallback<Integer, UserInfo> callback) {}}
同理它也有三个抽象方法需要重写:loadInitial、loadAfter、loadBefore方法,意义与 PositionalDataSource 中方法意义类型,都是加载第一页和第N页的逻辑,对于新增的 loadBefore代表在加载之前做的事情,个人现在用不到,感觉没什么太大的意义。
ItemKeyedUserDataSource
ItemKeyedUserDataSource也是继承自ItemKeyedDataSource抽象类,代码如下:
public class ItemKeyedUserDataSource extends ItemKeyedDataSource<Integer, UserInfo> {public static final int PAGE_SIZE = 20 ;@Overridepublic void loadInitial(@NonNull LoadInitialParams<Integer> params,@NonNull final LoadInitialCallback<UserInfo> callback) {RetrofitClient.getInstance().getApi().getUserListByItemPaged(0, PAGE_SIZE).enqueue(new Callback<UserModel>() {@Overridepublic void onResponse(@NonNull Call<UserModel> call, @NonNull Response<UserModel> response) {if(response.isSuccessful() && null != response.body()) {callback.onResult(response.body().data.userList);}}@Overridepublic void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {}});}@Overridepublic void loadAfter(@NonNull LoadParams<Integer> params, @NonNull final LoadCallback<UserInfo> callback) {RetrofitClient.getInstance().getApi().getUserListByItemPaged(params.key, PAGE_SIZE).enqueue(new Callback<UserModel>() {@Overridepublic void onResponse(@NonNull Call<UserModel> call, @NonNull Response<UserModel> response) {if(response.isSuccessful() && null != response.body()) {callback.onResult(response.body().data.userList);}}@Overridepublic void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {}});}@Overridepublic void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<UserInfo> callback) {}@NonNull@Overridepublic Integer getKey(@NonNull UserInfo item) {return item.userId;}
它有四个抽象方法需要重写:loadInitial、loadAfter、loadBefore、getKey。前三个方法和前面的意义类似,这里就不说了;对于getKey方法,就是我们获取下一页nextId的地方,也比较简单。
对于我们的 三种 DataSource获取完成之后,来看一下我们如果将数据展示在 RecyclerView 上了:
首先定义一个我们的SourceFactory:
public class UserDataSourceFactory extends DataSource.Factory<Integer, UserInfo> {// 这里可以根据需求换成另外两种DataSource即可。private MutableLiveData<PositionalUserDataSource> liveDataSource = new MutableLiveData<>();@NonNull@Overridepublic DataSource<Integer, UserInfo> create() {PositionalUserDataSource source = new PositionalUserDataSource();liveDataSource.postValue(source);return source;}}
然后定义一下我们的ViewModel
:
public class UserViewModel extends ViewModel {public LiveData<PagedList<UserInfo>> userPagedList;public UserViewModel() {PagedList.Config config = new PagedList.Config.Builder().// 用于控件占位setEnablePlaceholders(true).// 设置每页的大小setPageSize(PositionalUserDataSource.PAGE_SIZE).// 设置当距离底部还有多少条数据时开始加载下一页setPrefetchDistance(3).// 设置首次加载数据的数量 默认为 page_size 的三倍setInitialLoadSizeHint(PositionalUserDataSource.PAGE_SIZE * 3).// 设置pagedList 所能承受的最大数量setMaxSize(65536 * PositionalUserDataSource.PAGE_SIZE).build();userPagedList = new LivePagedListBuilder<>(new UserDataSourceFactory(),config).build();}}
设置并定义下PagedList.Config,其中几个比较重要的方法含义已经贴上去了。
最后,贴一下Adapter,一般使用Paging组件的话,都会使用androidx.paging.PagedListAdapter
:
public class UserPagedListAdapter extends PagedListAdapter<UserInfo, UserPagedListAdapter.UserItemViewHolder> {private Context mContext;public UserPagedListAdapter(Context context) {super(DIFF_CALLBACK);this.mContext = context;}private static DiffUtil.ItemCallback<UserInfo> DIFF_CALLBACK = new DiffUtil.ItemCallback<UserInfo>() {@Overridepublic boolean areItemsTheSame(@NonNull UserInfo oldItem, @NonNull UserInfo newItem) {return oldItem.userId == newItem.userId;}@Overridepublic boolean areContentsTheSame(@NonNull UserInfo oldItem, @NonNull UserInfo newItem) {return oldItem.userName.equals(newItem.userName) &&oldItem.userAvatar.equals(newItem.userAvatar) ;}};@NonNull@Overridepublic UserItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {return new UserItemViewHolder(LayoutInflater.from(mContext).inflate(R.layout.positional_user_item_layout,parent,false));}@Overridepublic void onBindViewHolder(@NonNull UserItemViewHolder holder, int position) {//TODO}static class UserItemViewHolder extends RecyclerView.ViewHolder {public UserItemViewHolder(@NonNull View itemView) {super(itemView);}}
}
最后在Activity中:
public class MainActivity extends AppCompatActivity {private RecyclerView mRecyclerView;private UserPagedListAdapter mPositionalAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mRecyclerView = findViewById(R.id.id_recycler_view);initPositionalAdapter();initPositionalObserve();}private void initPositionalAdapter() {mPositionalAdapter = new UserPagedListAdapter(this);mRecyclerView.setAdapter(mPositionalAdapter);mRecyclerView.setLayoutManager(new LinearLayoutManager(this));mRecyclerView.setHasFixedSize(true);}private void initPositionalObserve() {UserViewModel viewModel = new ViewModelProvider(this).get(UserViewModel.class);viewModel.userPagedList.observe(this, new Observer<PagedList<UserInfo>>() {@Overridepublic void onChanged(PagedList<UserInfo> userInfoPagedList) {mPositionalAdapter.submitList(userInfoPagedList);}});}
}
————————————————
版权声明:本文为CSDN博主「microhex」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013762572/article/details/107452801
Jetpack系列之 Paging 详解相关推荐
- Docker系列07—Dockerfile 详解
Docker系列07-Dockerfile 详解 1.认识Dockerfile 1.1 镜像的生成途径 基于容器制作 dockerfile,docker build 基于容器制作镜像,已经在上篇Do ...
- mongo 3.4分片集群系列之六:详解配置数据库
这个系列大致想跟大家分享以下篇章: 1.mongo 3.4分片集群系列之一:浅谈分片集群 2.mongo 3.4分片集群系列之二:搭建分片集群--哈希分片 3.mongo 3.4分片集群系列之三:搭建 ...
- ftm模块linux驱动,飞思卡尔k系列_ftm模块详解.doc
飞思卡尔k系列_ftm模块详解 1.5FTM模块1.5.1 FTM模块简介FTM模块是一个多功能定时器模块,主要功能有,PWM输出.输入捕捉.输出比较.定时中断.脉冲加减计数.脉冲周期脉宽测量.在K1 ...
- React Native按钮详解|Touchable系列组件使用详解
转载自:http://www.devio.org/2017/01/10/React-Native按钮详解-Touchable系列组件使用详解/ 在做App开发过程中离不了的需要用户交互,说到交互,我们 ...
- Material Design系列之BottomNavigationView详解
Material Design系列之BottomNavigationView详解 Material Design官方文档Bottom navigation的介绍 BottomNavigationVie ...
- React 源码系列 | React Context 详解
目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api.大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux). 想想项 ...
- Landsat系列数据级别详解
Landsat系列数据级别详解 转载自此文:https://www.cnblogs.com/icydengyw/p/12056211.html 一.Landsat Collection 1 Lands ...
- 小猫爪:i.MX RT1050学习笔记26-RT1xxx系列的FlexCAN详解
i.MX RT1050学习笔记26-RT1xxx系列的FlexCAN详解 1 前言 2 FlexCAN简介 2.1 MB(邮箱)系统 2.1.1 正常模式下 2.1.2 激活了CAN FD情况下 2. ...
- CISCO X8系列AP升级详解
X8系列AP升级详解 准备工作 1.将AP连接到可获取IP地址的设备:交换机.家用路由或者猫等 2.用终端通过CONSOLE线连接AP,默认用户名为:cisco 密码为:Cisco 3.在AP获取到地 ...
最新文章
- 笔记本应用/测试软件大全(个人使用心得)
- Hive基本操作,DDL操作(创建表,修改表,显示命令),DML操作(Load Insert Select),Hive Join,Hive Shell参数(内置运算符、内置函数)等
- Linux系统中为php添加pcntl扩展的方法
- List 集合去重的 3 种方法
- 【消息队列MQ】各类MQ比较
- Windows下配置Mysql免安装版
- 【OpenCV】OpenCV函数精讲之 -- copyTo()函数及Mask详解(附代码详解)
- linux top 中的内存 与 free,linux top命令下内存资源的讨论,高手请进。。。
- 深度学习模型在训练集上很好而在测试集表现得不好而拟合次数并不多_机器学习中的过拟合,欠拟合和偏倚方差折衷...
- POJ 3984 迷宫问题 (Dijkstra)
- 从代码书写理解指针,很重要
- 使用Flink实现索引数据到Elasticsearch
- js判断传入时间和当前时间大小
- UI Maker,界面设计sample
- java 实现打印机_JAVA实现连接本地打印机并打印文件的实现代码
- Chrome卸载重装
- 论文页码不连续?经常改不对?教你一招立马解决
- ASP.NET制作调查问卷
- 拉普拉斯-Laplacian
- linux中如何知道了主机IP,获得主机名