在此之前,我一直对Jetpack的Paging感觉到很迷茫,单单一个分页为啥 Android 官方会出一个组件? 在我们眼中,分页不就是添加两个参数pageSize和pageIndex 么?这么简单逻辑Android官方能耍得起什么样的波浪么?带着这个问题,阅读了一些文档,加上自己的理解,然后有了这篇文章。
Paging目前来说,是需要和RecyclerView配合使用的,毕竟Android 目前展示列表数据,差不多就是RecyclerView了。首先简单介绍一下Paging的工作原理,
完成以上步骤,涉及到几个类分别为:
以上的四个核心类理解完成之后,大致流程如下:
RecyclerView对应的Adapter为PagedListAdapter,通知PagedList需要获取数据,此时PagedList通过DataSource真正执行获取数据的逻辑,返回的数据给PagedList,然后PagedList将数据传递给PagedListAdapter,最后在RecyclerView中显示。
支持从任意位置开始,取多少条数据的方式,类似SQL中 " XX > id limit 100"
, 又类似 "start=100&count=20"
, 意味着从第100的位置开始,向后取20条数据。
这是我们最熟悉的模式,即"pageIndex=1&pageSize=20"
的模式。使用以”页“的方式请求数据。
”maxId=nextId&count=200“
模式,此次请求依赖上一次的的数据。这种请求方式一般在社交评论中用得比较多,只有这一次请求成功了,下一次请求才能依赖本次的某种参数
继续请求。
当然,这只是Google设计的三种用得比较多的请求方式,最终需要你选择的是你的服务器适合哪种方式,然后你再去采用这种方式。
首先我们定义接口:
按照上面三种类型分别定义三种接口
获取用户的列表,从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);
}
}
针对于 PositionalDataSource
,来看我们的代码:
public class PositionalUserDataSource extends PositionalDataSource<UserInfo> {
public static final int PAGE_SIZE = 20 ;
/**
* 首次加载数据
*/
@Override
public 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>() {
@Override
public 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);
}
}
}
@Override
public void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {
}
});
}
/**
*
* 第 N 页加载数据
*
* @param params
* @param callback
*/
@Override
public void loadRange(@NonNull LoadRangeParams params,
@NonNull final LoadRangeCallback<UserInfo> callback) {
RetrofitClient.
getInstance().
getApi().
getUserListByPositional(params.startPosition,PAGE_SIZE).
enqueue(new Callback<UserModel>() {
@Override
public void onResponse(@NonNull Call<UserModel> call, @NonNull Response<UserModel> response) {
if(response.isSuccessful() && null != response.body()){
callback.onResult(response.body().data.userList);
}
}
@Override
public 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
继承自抽象类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
*/
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params,
@NonNull final LoadInitialCallback<Integer, UserInfo> callback) {
RetrofitClient.getInstance().getApi().
getUserListByPageSize(FIRST_PAGE,FIRST_PAGE).
enqueue(new Callback<UserModel>() {
@Override
public 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);
}
}
@Override
public void onFailure(Call<UserModel> call, Throwable t) {
}
});
}
/**
* 加载下一页的数据
*
* @param params
* @param callback
*/
@Override
public 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>() {
@Override
public 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);
}
}
@Override
public void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {
}
});
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params,
@NonNull LoadCallback<Integer, UserInfo> callback) {
}
}
同理它也有三个抽象方法需要重写:loadInitial
、loadAfter
、loadBefore
方法,意义与 PositionalDataSource
中方法意义类型,都是加载第一页和第N页的逻辑,对于新增的 loadBefore
代表在加载之前做的事情,个人现在用不到,感觉没什么太大的意义。
ItemKeyedUserDataSource
也是继承自ItemKeyedDataSource
抽象类,代码如下:
public class ItemKeyedUserDataSource extends ItemKeyedDataSource<Integer, UserInfo> {
public static final int PAGE_SIZE = 20 ;
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params,
@NonNull final LoadInitialCallback<UserInfo> callback) {
RetrofitClient.
getInstance().
getApi().
getUserListByItemPaged(0, PAGE_SIZE).
enqueue(new Callback<UserModel>() {
@Override
public void onResponse(@NonNull Call<UserModel> call, @NonNull Response<UserModel> response) {
if(response.isSuccessful() && null != response.body()) {
callback.onResult(response.body().data.userList);
}
}
@Override
public void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {
}
});
}
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull final LoadCallback<UserInfo> callback) {
RetrofitClient.getInstance().
getApi().
getUserListByItemPaged(params.key, PAGE_SIZE).
enqueue(new Callback<UserModel>() {
@Override
public void onResponse(@NonNull Call<UserModel> call, @NonNull Response<UserModel> response) {
if(response.isSuccessful() && null != response.body()) {
callback.onResult(response.body().data.userList);
}
}
@Override
public void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {
}
});
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<UserInfo> callback) {}
@NonNull
@Override
public 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
@Override
public 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>() {
@Override
public boolean areItemsTheSame(@NonNull UserInfo oldItem, @NonNull UserInfo newItem) {
return oldItem.userId == newItem.userId;
}
@Override
public boolean areContentsTheSame(@NonNull UserInfo oldItem, @NonNull UserInfo newItem) {
return oldItem.userName.equals(newItem.userName) &&
oldItem.userAvatar.equals(newItem.userAvatar) ;
}
};
@NonNull
@Override
public UserItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new UserItemViewHolder(LayoutInflater.from(mContext).
inflate(R.layout.positional_user_item_layout,parent,false));
}
@Override
public 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;
@Override
protected 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>>() {
@Override
public void onChanged(PagedList<UserInfo> userInfoPagedList) {
mPositionalAdapter.submitList(userInfoPagedList);
}
});
}
}
基本原理大概就这么多了。