Paging就是Google为了方便Android开发者完成分页加载而设计的一个组件,分页加载是对数据进行按需加载,在不影响用户体验的同时,还能节省数据流量,提升应用的性能。
1.在RecyclerView的滑动过程中,会触发PagedListAdapter类中的onBindViewHolder()方法。数据与RecycleView Item布局中的UI控件正是在该方法中进行绑定的。
2.当RecyclerView滑动到底部时,在onBindViewHolder()方法中所调用的getItem()方法会通知PagedList,当前需要载入更多数据。
3.接着,PagedList会根据PageList.Config中的配置通知DataSource执行具体的数据获取工作。
4.DataSource从网络/本地数据库取得数据后,交给PagedList,PagedList将持有这些数据。
5.PagedList将数据交给PagedListAdapter中的DiffUtil进行比对和处理。
6.数据在经过处理后,交由RecyclerView进行展示。
public class FansDataSource extends PageKeyedDataSource<Integer, Fans.DataBean.ListBean> {
public static final int FIRST_PAGE =0;
public static final int PER_PAGE=20;
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Fans.DataBean.ListBean> callback) {
MyApplication.getApiService().getfans("application/json",MyApplication.accesstoken,MyApplication.open_id.getValue(),FIRST_PAGE,PER_PAGE).enqueue(new Callback<Fans>() {
@Override
public void onResponse(Call<Fans> call, Response<Fans> response) {
if(response.body()!=null)
{
callback.onResult(response.body().getData().getList(),null,FIRST_PAGE+response.body().getData().getCursor());
}
}
@Override
public void onFailure(Call<Fans> call, Throwable t) {
}
});
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Fans.DataBean.ListBean> callback) {
}
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Fans.DataBean.ListBean> callback) {
MyApplication.getApiService().getfans("application/json",MyApplication.accesstoken,MyApplication.open_id.getValue(),params.key,PER_PAGE).enqueue(new Callback<Fans>() {
@Override
public void onResponse(Call<Fans> call, Response<Fans> response) {
if(response.body()==null)
{return;}
Integer nextKey=response.body().getData().isHas_more()?response.body().getData().getCursor():null;
callback.onResult(response.body().getData().getList(),nextKey);
}
@Override
public void onFailure(Call<Fans> call, Throwable t) {
}
});
}
}
当页面首次加载数据时会调用loadInitial()方法。在该方法内调用API接口,加载第一页的数据。加载成功后,通过callback.onResult()方法将数据返回给PagedList。
第1个参数是加载得到的数据,将其交给PagedList。第2个参数是上一页的Pagekey。在此,由于当前加载的是第一页,不存在上一页,所以设置为null。第3个参数为下一页的Pagekey,即当前页key的值加上第一次请求返回的cursor,若不存在下一页,则设置为null。
加载下一页的工作在该方法内进行。需要注意的是LoadParamsparams参数,我们在loadInitial()方法中设置的nextPageKey,正是通过LoadParams传递过来的。LoadParams.key得到的是下一页的key,通过这个key,我们可以请求下一页。请求下一页成功后,同样也是通过callback.onResult()方法将数据返回给PagedList,同时再设置下一页的key。注意:在设置下一页之前,需要判断是否还有更多的数据,若没有数据,则将下一页的key设置为null,表示所有数据请求完毕。
FansDataSourceFactory负责创建FansDataSource,并使用LiveData包装FansDataSource,将其暴露给FansViewModel。
public class FansDataSourceFactory extends DataSource.Factory<Integer, Fans.DataBean.ListBean> {
private MutableLiveData<FansDataSource>fansDataSourceMutableLiveData=new MutableLiveData<>();
@NonNull
@Override
public DataSource<Integer, Fans.DataBean.ListBean> create() {
FansDataSource dataSource=new FansDataSource();
fansDataSourceMutableLiveData.postValue(dataSource);
return dataSource;
}
}
在FansViewModel中通过LivePagedListBuilder创建和配置PagedList,并使用LiveData包装PagedList,将其暴露给Activity。
public class FansViewModel extends ViewModel {
public LiveData<PagedList<Fans.DataBean.ListBean>> fansPagedList;
public FansViewModel()
{
config=(new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setPageSize(FansDataSource.PER_PAGE)
.setPrefetchDistance(40)
.setMaxSize(1000*FansDataSource.PER_PAGE)
.build();
}
public LiveData<PagedList<Fans.DataBean.ListBean>> getFansPagedList()
{
fansPagedList=(new LivePagedListBuilder<>(new FansDataSourceFactory(),config)).build();
return fansPagedList;
}
关于PagedList.Config中的几个重要方法说明如下。
● setEnablePlaceholders:用于设置控件占位,上文已详细介绍过。
● setPageSize:设置每页的大小,该值通常与DataSource中请求数据的参数值保持一致。
● setPrefetchDistance:设置当距离底部还有多少条数据时开始加载下一页数据。
● setInitialLoadSizeHint:设置首次加载数据的数量。该值要求是PageSize的整数倍。若未设置,则默认是PageSize的3倍。
● setMaxSize:设置PagedList所能承受的最大数量,一般来说是PageSize的许多倍,超过该值可能会出现异常。
列表数据通过FansPagedListAdapter进行展示
public class FansPagedListAdapter extends PagedListAdapter<Fans.DataBean.ListBean,FansPagedListAdapter.FansViewHolder> {
private Context context;
public FansPagedListAdapter(Context context)
{
super(DIFF_CALLBACK);
this.context=context;
}
private static DiffUtil.ItemCallback<Fans.DataBean.ListBean> DIFF_CALLBACK=new DiffUtil.ItemCallback<Fans.DataBean.ListBean>() {
@Override
public boolean areItemsTheSame(@NonNull Fans.DataBean.ListBean oldItem, @NonNull Fans.DataBean.ListBean newItem) {
return oldItem.getOpen_id()==newItem.getOpen_id();
}
@SuppressLint("DiffUtilEquals")
@Override
public boolean areContentsTheSame(@NonNull Fans.DataBean.ListBean oldItem, @NonNull Fans.DataBean.ListBean newItem) {
return oldItem.equals(newItem);
}
};
@NonNull
@Override
public FansPagedListAdapter.FansViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
FragmentFansListBinding fragmentFansListBinding= DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.fragment_fans_list,parent,false);
return new FansViewHolder(fragmentFansListBinding);
}
@Override
public void onBindViewHolder(@NonNull FansPagedListAdapter.FansViewHolder holder, int position) {
Fans.DataBean.ListBean listBean=getItem(position);
if(listBean!=null)
holder.fragmentFansListBinding.setFans(listBean);
}
class FansViewHolder extends RecyclerView.ViewHolder
{
FragmentFansListBinding fragmentFansListBinding;
public FansViewHolder(@NonNull FragmentFansListBinding itemView) {
super(itemView.getRoot());
fragmentFansListBinding=itemView;
}
}
}
FansPagedListAdapter需要继承自PagedListAdapter。在onBindViewHolder()方法中调用getItem()方法。若当前有数据,则直接将数据与UI控件进行绑定;若没有数据,则getItem()会通知PagedList去获取下一页的数据,PagedList收到通知后,让DataSource执行具体的数据获取工作。
在onCreateViewHolder根据布局文件获取到相对应的databinding类在,onBindViewHolder直接将拿到的list通过databinding类传入布局文件,在布局文件就可以直接将传递过来的list进行展示。
DiffUtil的使用主要涉及两个方法,我们需要覆盖这两个方法。正是这两个方法,让更新数据变得更高效。
● areItemsTheSame:当DiffUtil想要检测两个对象是否代表同一个Item时,调用该方法进行判断。
● areContentsTheSame:当DiffUtil想要检测两个Item是否存在不一样的数据时,调用该方法进行判断。
最后在Activity中我们只要将recyclerview获取到为其设定对应的适配器,并观察fansViewModel中数据的变化,若数据发生变化,则通过PagedListAdapter.submitList()方法刷新数据。
final FansPagedListAdapter fansPagedListAdapter = new FansPagedListAdapter(getContext());
fansViewModel.getFansPagedList().observe(getViewLifecycleOwner(), new Observer<PagedList<Fans.DataBean.ListBean>>() {
@Override
public void onChanged(PagedList<Fans.DataBean.ListBean> listBeans) {
fansPagedListAdapter.submitList(listBeans);
Log.d(TAG, listBeans.toString());
}
});