title: Android分页组件Paging简单使用
date: 2018-10-10 17:03:23
tags: Paging
1.简介:
Paging组件是Google新推出的分页组件,可以轻松帮助开发者实现RecyclerView中分页加载功能。
本文先开坑,等以后用到在详细写明。
推荐博客:https://www.jianshu.com/p/1bfec9b9612c
2.Paging库引入:
implementation 'android.arch.paging:runtime:1.0.0'
刚引入就遇到了错误,真是一个不友好的开始:
Error:Execution failed for task ':app:transformDexArchiveWithExternalLibsDexMergerForDebug'.
> java.lang.RuntimeException: java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Unable to merge dex
一查资料,是导入重复的包,这个出错的地方还是google自己的东西,这岂不是
“大水冲了龙王庙,自家人打自家人”?
compile 'com.android.support:design:26.1.0'
google了一下,在stackoverflow中找到了解决办法:
https://stackoverflow.com/questions/49028119/multiple-dex-files-define-landroid-support-design-widget-coordinatorlayoutlayou
即用编译版本改成27,并且把对应的库版本也改为27
implementation 'com.android.support:appcompat-v7:27.1.1'
compile 'com.android.support:design:27.1.1'
问题了解决了,接下来我们看看他的工作原理
3.工作原理

这是官方提供的原理图,写的很清楚从DataSource到PagedList, PagedListAdapter最后是我们的recyclerview,我们先眼熟这几个名字,下文会常常出现。
3.1 先来看Adapter
public class AdapterPaging extends PagedListAdapter<VideoInfo, AdapterPaging.ViewHolder> {
private Context mContext;
public static final DiffUtil.ItemCallback<VideoInfo> mDiffCallback = new AdapterPaging.VideoInfoItemCallback();
protected AdapterPaging() {
super(mDiffCallback);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
VideoInfo videoInfo = getItem(position);
}
static class ViewHolder extends RecyclerView.ViewHolder{
private LinearLayout ll_videoInfo;
public ViewHolder(View itemView) {
super(itemView);
ll_videoInfo = itemView.findViewById(R.id.ll_video_info);
}
}
private static class VideoInfoItemCallback extends DiffUtil.ItemCallback<VideoInfo>{
@Override
public boolean areItemsTheSame(VideoInfo oldItem, VideoInfo newItem) {
return oldItem.getUrl() == newItem.getUrl();
}
@Override
public boolean areContentsTheSame(VideoInfo oldItem, VideoInfo newItem) {
return (oldItem == newItem);
}
}
}
大致的4个不同如下:
- Adapter不再继承自RecyclerView.Adapter,改为继承自PagedListAdapter,因为PagedListAdapter就是RecyclerView.Adapter的一个子类。
- 定义内部回调接口VideoInfoItemCallback继承自DiffUtil.ItemCallback<VideoInfo>,并且实例化一个父类引用指向子类(VideoInfoItemCallback)对象
public static final DiffUtil.ItemCallback<VideoInfo> mDiffCallback = new AdapterPaging.VideoInfoItemCallback();
- 重写构造方法,无需参数传入,调用父类构造方法将mDiffCallback传入。
- 通onBindViewHolder中过调用getItem(position);获得指定位置的数据对象。
因为Adapter中不再需要维护一个数据List了,PagedListAdapter中已经维护有,并且提供getItem()方法访问。
3.2 在Activity中的使用
在使用Paging后,我们无需向Adapter中在传入数据源List,我们需要构造LiveData。
LiveData需要DataSource.Factory对象和PagedList.Config对象,只是实例化DataSource.Factory对象需要额外两个步骤
DataSource.Factory是一个抽象类,实例化时需要实现create()函数,这个函数返回值是一个DataSource类对象
DataSource是一个抽象类,他有三个实现子类:(详细参考原博客:https://www.jianshu.com/p/95d44c5338fd)
(1)PageKeyedDataSource 按页加载,如请求数据时传入page页码。
(2)ItemKeyedDataSource 按条目加载,即请求数据需要传入其它item的信息,如加载第n+1项的数据需传入第n项的id。
(3)PositionalDataSource 按位置加载,如加载指定从第n条到n+20条。
所以我们捋一下思路,首先要定义一个MyDataSource继承自DataSource的三个子类之一,
再定义一个MyDataSourceFactory继承自DataSource.Factory,返回值是MyDataSource
然后实例化PagedList.Config,这个类提供有Builder(),比较简单。
最后将MyDataSourceFactory对象和PagedList.Config对象传入new LivePagedListBuilder()中得到liveData数据源。
将liveData数据源和Adapter绑定是通过观察者模式实现,调用liveData.observe()。
//定义MyDataSource类,继承自DataSource三个子类之一
private class MyDataSource extends PositionalDataSource<Movie.SubjectsBean>{
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Movie.SubjectsBean> callback) {
callback.onResult(loadData(0, 10), 0, 10);//loadData(0, 10)是我封装的加载数据方法,网络请求在loadData中,0是初始位置,10是请求10条数据
}
@Override
public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Movie.SubjectsBean> callback) {
callback.onResult(loadData(params.startPosition, count));
}
}
//定义MyDataSourceFactory,是DataSource.Factory的实现类
private class MyDataSourceFactory extends DataSource.Factory<Integer, Movie.SubjectsBean>{
@Override
public DataSource<Integer, Movie.SubjectsBean> create() {
return new MyDataSource();
}
}
//将生产config和liveData的代码封装在这个方法中
private void initPaging(){
PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(10) //每页显示的词条数
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(10) //首次加载的数据量
.setPrefetchDistance(5) //距离底部还有多少条数据时开始预加载
.build();
/**
* LiveData用LivePagedListBuilder生成
* LivePagedListBuilder 构造方法需要 DataSource.Factory和PagedList.Config
*/
LiveData<PagedList<Movie.SubjectsBean>> liveData = new LivePagedListBuilder(new MyDataSourceFactory(), config)
.build();
//观察者模式,将Adapter注册进去,当liveData发生改变事通知Adapter
liveData.observe(this, new Observer<PagedList<Movie.SubjectsBean>>() {
@Override
public void onChanged(@Nullable PagedList<Movie.SubjectsBean> subjectsBeans) {
adapterHomeInfo.submitList(subjectsBeans);
}
});
}
从上面的代码我们可以看到,数据的入口在MyDataSource的两个重写方法里,里面的loadData()就是从外部获取数据的方法,可以按自己想需求来封装。但是数据加载位于这个位置,就要求我们初始化的时候使用的是本地数据,而来不及进行网络请求。
4.加载网络数据
这么好的控件,我无论如何都想用于纯网络加载。想这么做也很简单,因为我们已经把与初始化相关的代码封装到initPaging()中了,
我们只需要在合适的时候进行初始化,就能达到延迟加载的目的了。比如,我们可以用handler和网络请求实现消息异步处理,在handler的
中调用initPaging(),这样就可以不需要本地数据进行初始化了。
private boolean isDataFresh = false;
private boolean isInitPaging = false;
private Movie movie;
private MyHandler handler = new MyHandler(this);
//定义一个MyHandler,用弱引用防止内存泄漏
private static class MyHandler<T> extends Handler {
WeakReference<ActivityHome> weakReference;
public MyHandler(ActivityHome activityHome){
weakReference = new WeakReference<ActivityHome>(activityHome);
}
@Override
public void handleMessage(Message msg) {
ActivityHome activity = weakReference.get();
//网络状况不好时,返回obj为null
if(null == msg.obj){
Toast.makeText(activity, "获取数据失败,请检查网络", Toast.LENGTH_SHORT).show();
return;
}
switch (msg.what){
case Constants._MOVIE_REQUEST:
activity.movie = NetWorkUtil.parseJsonWithGson(msg.obj.toString(), Movie.class);
activity.isDataFresh = true;
if (!activity.isInitPaging) {
activity.initPaging();
activity.isInitPaging = true;
}
break;
default:
break;
}
}
}
/**
* 进行网络请求
*
* @param startPosition
* @param count
* @return
*/
private List<Movie.SubjectsBean> loadData(int startPosition, int count){
List<Movie.SubjectsBean> subjectsBeanList = new ArrayList<>();
if (true == isDataFresh) {
subjectsBeanList = movie.getSubjects();
//显示数据之后,将标志设置为false,等待下一次网络请求,数据更新完成
isDataFresh = false;
//启动下一次数据刷新
requestData(handler);
}
return subjectsBeanList;
}
这里我用了两个标志位isDataFresh和isInitPaging,
isInitPaging是确保只在初始化时调用initPaging(),
isDataFresh是确保当前网络请求完成后,才会发送下一次请求,防止同时发送多个网络请求。
网友评论