Jetpack 架构组件 Paging 分页加载 MD
| Markdown版本笔记 | 我的GitHub首页 | 我的博客 | 我的微信 | 我的邮箱 |
|---|---|---|---|---|
| MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
目录
Paging
简介
Paging 是什么?
Paging 可以使开发者更轻松在 RecyclerView 中 分页加载数据。
implementation "android.arch.paging:runtime:1.0.1" //Paging
implementation "android.arch.paging:rxjava2:1.0.1" //Paging对RxJava2的支持
原理示意图:https://upload-images.jianshu.io/upload_images/7293029-27facf0a399c66b8.gif?imageMogr2/auto-orient/
组成部分:
- DataSource:数据源,
数据的改变会驱动列表的更新,因此,数据源是很重要的 - PageList:核心类,它从数据源取出数据,同时,它负责控制
第一次默认加载多少数据,之后每一次加载多少数据,如何加载等等,并将数据的变更反映到UI上。 - PagedListAdapter:适配器,RecyclerView的适配器,通过分析数据是否发生了改变,负责处理UI展示的逻辑(增加/删除/替换等)。
使用步骤
创建数据源
在Paging中,数据源被抽象为 DataSource , 其获取需要依靠 DataSource 的内部工厂类 DataSource.Factory ,通过create()方法就可以获得DataSource 的实例:
public abstract static class Factory<Key, Value> {
public abstract DataSource<Key, Value> create();
}
数据源一般有两种选择,远程服务器请求或者读取本地持久化数据,这些并不重要,本文我们以Room数据库为例:
@Query("SELECT * FROM table_user")
DataSource.Factory<Integer, User> getAllUserDataSource();
DataSource.Factory<Integer, User> factory = UserDb.get(getApplication()).userDao().getAllUserDataSource();
Paging可以获得Room的原生支持,因此作为示例非常合适,当然我们更多获取数据源是通过API网络请求,其实现方式可以参考 官方Sample。
PS:如果通过API网络请求获取DataSource,相比使用Room来说要麻烦很多
配置PageList
PageList的作用:
- 从数据源取出数据
- 负责控制第一次默认加载多少数据,之后每一次加载多少数据,如何加载等等
- 将数据的变更反映到UI上
PageList提供了 PagedList.Config 类供我们进行实例化配置,其提供了5个可选配置:
public static final class Builder {
// 省略Builder其他内部方法
private int mPageSize = -1; //每次加载多少数据
private int mPrefetchDistance = -1; //距底部还有几条数据时,加载下一页数据
private int mInitialLoadSizeHint = -1; //第一次加载多少数据,必须是分页加载数量的倍数
private boolean mEnablePlaceholders = true; //是否启用占位符,若为true,则视为固定数量的item
private int mMaxSize = MAX_SIZE_UNBOUNDED; //默认Integer.MAX_VALUE,Defines how many items to keep loaded at once.
}
配置Adapter
就像我们平时配置 RecyclerView 差不多,我们配置了 ViewHolder 和 RecyclerView.Adapter,略微不同的是,我们需要继承PagedListAdapter,并且我们需要传一个 DifffUtil.ItemCallback 的实例。
DifffUtil.ItemCallback的意义是,我需要知道怎么样的比较,才意味着数据源的变化,并根据变化再进行的UI刷新操作。
监听数据源的变更,并响应在UI上
这个就很简单了
//每当观察到数据源中数据的变化,我们就把最新的数据交给Adapter去展示
viewModel.getRefreshLiveData().observe(this, pagedList -> {
Log.i("bqt", "【数据发生改变】" + pagedList.size() + " "
+ pagedList.getPositionOffset() + " " + pagedList.getLoadedCount() + " " + pagedList.getLastKey() + " "
+ pagedList.isImmutable() + " " + pagedList.isDetached());
adapter.submitList(pagedList); //将数据的变化反映到UI上 Set the new list to be displayed
});
PageKeyedDataSource

基本结构:
//这个数据源主要需要传递Int型的PageNum作为参数实现每一页数据的请求
public class PagingDataSource extends PageKeyedDataSource<Integer, User> {
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, User> callback) {
//requestedLoadSize为加载的数据量,placeholdersEnabled是是否显示占位;callback为数据加载完成的回调
//LoadInitialCallback的onResult方法有三个参数,第一个为数据,后面两个即为上一页和下一页
Log.i("bqt", "【loadInitial】" + params.requestedLoadSize + " " + params.placeholdersEnabled);//初始加载数据
//if(满足条件) 请求一批数据,数据处理后通过callback返回
//callback.onResult(List<Value> data, Key previousPageKey, Key nextPageKey);
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, User> callback) {
Log.i("bqt", "【loadBefore】" + params.key + " " + params.requestedLoadSize);//向前分页加载数据
//key即为DataSource<Key, Value>中的key,在这里即为页数;同样,callback为数据加载完成的回调
//LoadParams中的key即为我们要加载页的数据,加载完后回调中告知下一次加载数据页数+1或者-1
}
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, User> callback) {
Log.i("bqt", "【loadAfter】" + params.key + " " + params.requestedLoadSize);//向后分页加载数据
//if(满足条件) 再请求一批数据,数据处理后通过callback返回
//callback.onResult(List<Value> data, Key adjacentPageKey);
}
}
继承自PageKeyedDataSource后需要实现以下三个方法:
loadInitial初始加载数据loadAfter向后分页加载数据loadBefore向前分页加载数据
这三个方法都有两个参数,一个params和一个callback 。
- params包装了分页加载的参数:
- loadInitial中的params为
LoadInitialParams包含了requestedLoadSize和placeholdersEnabled两个属性,requestedLoadSize为加载的数据量,placeholdersEnabled是是否显示占位及当数据为null时显示占位的view - loadBefore和loadAfter中的params为
LoadParams包含了key和requestedLoadSize,key即为DataSource<Key, Value>中的key,在这里即为页数
- loadInitial中的params为
- callback为数据加载完成的回调,loadInitial中调用调用IPVTApiPresenter加载数据,然后调用
callback.onResult告诉调用者数据加载完成。
onResult有三个参数,第一个为数据,后面两个即为上一页和下一页。
如果我们当前页为第一页即没有上一页,则上一页为null,下一页为2,此时加载的时候会加载当前页和调用loadAfter加载第二页,但不会调用loadBefore,因为没有上一页,即previousPageKey为null不会加载上一页
如果我们初始加载的是第三页,则上一页是2,下一页是4,此时加载的时候会加载当前页和调用loadAfter加载第4页,调用loadBefore加载第二页
分页加载的时候会将previousPageKey或nextPageKey传递到loadAfter或loadBefore中的params.key
loadAfter 、loadBefore中的params中的key即为我们要加载页的数据,加载完后回调中告知下一次加载数据页数+1或者-1
Java版案例
参考 此博客,原文为Kotlin版案例,我将其转为Java版实现,并在其基础上添加了一些逻辑。
PagingActivity
public class PagingActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_paging);
RecyclerView recyclerView = findViewById(R.id.recyclerView);
DiffUtil.ItemCallback<User> itemCallback = new DiffUtil.ItemCallback<User>() {
@Override
public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) {
return oldItem.uid == newItem.uid;
}
@Override
public boolean areContentsTheSame(@NonNull User oldItem, @NonNull User newItem) {
return oldItem == newItem;
}
};
PagingAdapter adapter = new PagingAdapter(itemCallback);
UserDao dao = UserDb.get(this).userDao();
adapter.setOnClick((user, position) -> {
Log.i("bqt", "【position】" + position);
new Thread(() -> {
if (position % 2 == 0) dao.deleteUser(user);
else dao.insertUser(new User("insert"));
}).start();
});
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
dao.getAllUser().observe(this, users -> Log.i("bqt", "【数据发生改变】" + users.size()));
PagingViewModel viewModel = ViewModelProviders.of(this).get(PagingViewModel.class);
//每当观察到数据源中数据的变化,我们就把最新的数据交给Adapter去展示
viewModel.getRefreshLiveData().observe(this, pagedList -> {
Log.i("bqt", "【数据发生改变】" + pagedList.size() + " "
+ pagedList.getPositionOffset() + " " + pagedList.getLoadedCount() + " " + pagedList.getLastKey() + " "
+ pagedList.isImmutable() + " " + pagedList.isDetached());
adapter.submitList(pagedList); //将数据的变化反映到UI上 Set the new list to be displayed
});
}
}
PagingAdapter
public class PagingAdapter extends PagedListAdapter<User, PagingAdapter.MyViewHolder> {
PagingAdapter(DiffUtil.ItemCallback<User> itemCallback) {
super(itemCallback);
}
@Override
public void onCurrentListChanged(@Nullable PagedList<User> previousList, @Nullable PagedList<User> currentList) {
super.onCurrentListChanged(previousList, currentList);
Log.i("bqt", "【onCurrentListChanged】");
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_student, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
Log.i("bqt", "【onBindViewHolder】" + position);
User user = getItem(position);
//items might be null if they are not paged in yet. PagedListAdapter will re-bind the ViewHolder when Item is loaded.
if (user != null) {
holder.nameView.setText(user.name);
holder.nameView.setOnClickListener(v -> {
if (onClick != null) {
onClick.onClick(user, position);
}
});
}
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView nameView;
MyViewHolder(View view) {
super(view);
nameView = view.findViewById(R.id.name);
}
}
private OnClick onClick;
void setOnClick(OnClick onClick) {
this.onClick = onClick;
}
interface OnClick {
void onClick(User user, int position);
}
}
PagingViewModel
public class PagingViewModel extends AndroidViewModel {
public PagingViewModel(@NonNull Application application) {
super(application);
}
public LiveData<PagedList<User>> getRefreshLiveData() {
DataSource.Factory<Integer, User> dataSourceFactory = UserDb.get(getApplication()).userDao().getAllUserDataSource();
PagedList.Config config = new PagedList.Config.Builder()
.setInitialLoadSizeHint(10) //第一次加载多少数据,必须是分页加载数量的倍数
.setPageSize(5) //每次加载多少数据
.setMaxSize(Integer.MAX_VALUE) //Defines how many items to keep loaded at once.
.setPrefetchDistance(5) //距底部还有几条数据时,加载下一页数据
.setEnablePlaceholders(true) //是否启用占位符,若为true,则视为固定数量的item
.build();
LivePagedListBuilder<Integer, User> livePagedListBuilder = new LivePagedListBuilder<>(dataSourceFactory, config)
.setFetchExecutor(Executors.newSingleThreadExecutor()) //设置获取数据源的线程
.setInitialLoadKey(0) //可通过 pagedList.getLastKey() 获取此值,默认值当然为 Key(这里为Integer)类型的初始化值()这里为0
.setBoundaryCallback(new PagedList.BoundaryCallback<User>() {
@Override
public void onZeroItemsLoaded() { //没有数据被加载
super.onZeroItemsLoaded();
Log.i("bqt", "【onZeroItemsLoaded】");
}
@Override
public void onItemAtFrontLoaded(@NonNull User itemAtFront) { //加载第一个
super.onItemAtFrontLoaded(itemAtFront);
Log.i("bqt", "【onItemAtFrontLoaded】" + itemAtFront.name);
}
@Override
public void onItemAtEndLoaded(@NonNull User itemAtEnd) { //加载最后一个
super.onItemAtEndLoaded(itemAtEnd);
Log.i("bqt", "【onItemAtEndLoaded】" + itemAtEnd.name);
}
});
return livePagedListBuilder.build();
}
}
User
@Entity(tableName = "table_user")
public class User {
@PrimaryKey(autoGenerate = true) public int uid;
@ColumnInfo(name = "user_name") public String name = "包青天";
public User(String name) {
this.name = name;
}
}
UserDao
@Dao
public interface UserDao {
@Insert
List<Long> insertUser(User... users);
@Insert
List<Long> insertUser(List<User> users);
@Delete
int deleteUser(User user);
@Query("SELECT * FROM table_user")
LiveData<List<User>> getAllUser();
@Query("SELECT * FROM table_user")
DataSource.Factory<Integer, User> getAllUserDataSource();
}
UserDb
@Database(entities = {User.class}, version = 1)
public abstract class UserDb extends RoomDatabase {
public abstract UserDao userDao(); //没有参数的抽象方法,返回值所代表的类必须用@Dao注解
private static UserDb db;
public static UserDb get(Context context) {
if (db == null) {
db = Room.databaseBuilder(context.getApplicationContext(), UserDb.class, "dbname")
.addCallback(new RoomDatabase.Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase database) {
super.onCreate(database);
Log.i("bqt", "【onCreate】");
new Thread(() -> {
List<User> users = new ArrayList<>();
for (int i = 0; i < 50; i++) {
users.add(new User("bqt" + i));
}
get(context).userDao().insertUser(users);
}).start();
}
})
.build();
}
return db;
}
}
2019-4-7
附件列表
Jetpack 架构组件 Paging 分页加载 MD的更多相关文章
- Jetpack系列:Paging组件帮你解决分页加载实现的痛苦
相信很多小伙伴们在项目实战中,经常会用到界面的分页显示.加载更多等功能.需要针对具体功能做针对性开发和调试,耗时耗力. Paging组件的使用将这部分的工作简化,从而让开发者更专注于业务的具体实现.下 ...
- Android Jetpack 架构组件最佳实践之“网抑云”APP
背景 近几年,Android 相关的新技术层出不穷.往往这个技术还没学完,下一个新技术又出来了.很多人都是一脸黑人问号? 不少开发者甚至开始哀嚎:"求求你们别再创造新技术了,我们学不动了!& ...
- Jetpack架构组件学习(4)——APP Startup库的使用
最近在研究APP的启动优化,也是发现了Jetpack中的App Startup库,可以进行SDK的初始化操作,于是便是学习了,特此记录 原文:Jetpack架构组件学习(4)--App Startup ...
- java攻城狮之路(Android篇)--widget_webview_metadata_popupwindow_tabhost_分页加载数据_菜单
一.widget:桌面小控件1 写一个类extends AppWidgetProvider 2 在清单文件件中注册: <receiver android:name=".ExampleA ...
- [转]微信小程序之加载更多(分页加载)实例 —— 微信小程序实战系列(2)
本文转自;http://blog.csdn.net/michael_ouyang/article/details/56846185 loadmore 加载更多(分页加载) 当用户打开一个页面时,假设后 ...
- ListView实现分页加载(三)实现分页加载
在上一篇中,我们实现了底部布局(即带上了进度条).没有读过的朋友可以点击下面的链接: http://www.cnblogs.com/fuly550871915/p/4866966.html 但是进度条 ...
- ListView实现分页加载(一)制作Demo
一.什么是分页加载 在下面的文章中,我们来讲解LitView分页加载的实现.什么是分页加载呢?我们先看几张效果图吧,如下: ...
- android中滑动SQLite数据库分页加载
今天用到了android中滑动SQlit数据库分页加载技术,写了个测试工程,将代码贴出来和大家交流一下: MainActivity package com.example.testscrollsqli ...
- Jetpack架构组件学习(2)——ViewModel和Livedata使用
要看本系列其他文章,可访问此链接Jetpack架构学习 | Stars-One的杂货小窝 原文地址:Jetpack架构组件学习(2)--ViewModel和Livedata使用 | Stars-One ...
随机推荐
- 事件监听addEventListener----attachEvent
第一:简单的通用方法(IE && FF) window.onload = function(){ var oDiv = document.getElementById("J_ ...
- 3186Treats for the Cows(区间dp)
题意:给一个数组v,每次可以取前面的或者后面的,第k次取的v[i]价值为v[i]*k,问总价值最大是多少. 区间dp. 区间dp可以不枚举len 直接枚举i和j即可 见代码 #include &l ...
- 无法启动 Maya 集成的 qt designer 的解决方法和原因 以及 中英文切换
无法启动 Maya 集成的 qt designer 的解决方法和原因 以及 中英文切换 前言: Maya 集成了 PySide,同时集成了qt designer,在 Maya 的安装目录下的 bin ...
- [ 中危 ] dp意见反馈处存储型XSS
XSS平台架设攻击代码,有很多,如我是在http://xss.fbisb.com上架设的. 在 xxx.dianping.com系统意见反馈处插入xss代码提交,而后等待后台管理员点击,可打到其COO ...
- Jenkins环境搭建(1)-下载与安装
Jenkins简介 Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目,它是一个免费的源代码,可以处理任何类型的构建或持续集成.集成Jenkins可以用于一些测试和部署技术. Jenk ...
- php手撸轻量级开发(三)composer小白入门
composer介绍 composer是php的一个依赖管理工具,在项目中声明所依赖的外部工具库,会自动安装这些工具库及所依赖的库文件. 对我等初阶工程狮来说,就是拉别人的代码,用别人的库,一个进阶版 ...
- PhantomJS在Selenium中被标记为过时的应对措施
今天使用PhantomJS时,Selenium提示PhantomJS被标记不赞成,我就蒙了.PhantomJS可是Headless浏览器中相当知名的一款,标记为过时,代表着将在未来版本摒弃掉这个支持. ...
- Spring监听,ApplicationListener
import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; import ...
- vue中的页面渲染方案
一.模板渲染 <div id="J_render_app"> <ul v-if="items.length"> <li v-for ...
- angular笔记_3
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...