当前位置:   article > 正文

Jetpack系列之 Paging 详解_jetpack paging

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


大概的返回结果我们也可以定义一下:
 

  1. {
  2. "code":200,
  3. "msg": "success",
  4. "data": {
  5. "total":"100",
  6. "userList" : [
  7. {
  8. "userId":1,
  9. "userName":"tom",
  10. "userAge": 20,
  11. "userAvatar" : "http://api.com/user/avatar.png"
  12. },
  13. {
  14. "userId":2,
  15. "userName":"Jerry",
  16. "userAge": 18,
  17. "userAvatar" : "http://api.com/user/avatar2.png"
  18. }
  19. ]
  20. }
  21. }

针对三种API,Paging分别提供了三个抽象类:PositionalDataSourcePageKeyedDataSourceItemKeyedDataSource满足以上接口分类。我们一个一个来。先看一下我们的项目目录结构:

使用到的 dependences为:

  1.     implementation 'com.squareup.retrofit2:retrofit:2.7.2'
  2.     implementation 'com.squareup.retrofit2:converter-gson:2.7.2'
  3.     implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
  4.     implementation 'androidx.paging:paging-runtime:2.1.2'
  5.     implementation 'androidx.recyclerview:recyclerview:1.1.0'


首先定义Model类:
 

  1. public class UserInfo {
  2. public int userId;
  3. public String userName;
  4. public String userAvatar;
  5. }
  6. public class UserModel {
  7. public int code;
  8. public String msg;
  9. public UserList data;
  10. public static class UserList{
  11. public int total;
  12. public List<UserInfo> userList;
  13. }
  14. }

 然后定义我们的Api类:

  1. public interface Api {
  2. @GET("/userList/")
  3. Call<UserModel> getUserListByPositional(@Query("start") int start,
  4. @Query("count") int count);
  5. @GET("/userList2/")
  6. Call<UserModel> getUserListByPageSize(@Query("pageIndex") int pageIndex,
  7. @Query("pageSize") int pageSize);
  8. @GET("/userList3/")
  9. Call<UserModel> getUserListByItemPaged(@Query("nextId") int nextId,
  10. @Query("pageCount") int pageSize);
  11. }

然后定义我们的 RetrofitClient类,都比较简单,贴一下代码:

  1. public class RetrofitClient {
  2. private static final String BASE_URL = "https://your.host.url";
  3. private static RetrofitClient instance ;
  4. private Retrofit mRetrofit ;
  5. private RetrofitClient() {
  6. mRetrofit = new Retrofit.Builder().
  7. baseUrl(BASE_URL).
  8. addConverterFactory(GsonConverterFactory.create()).
  9. client(new OkHttpClient()).build();
  10. }
  11. public synchronized static RetrofitClient getInstance() {
  12. if(null == instance) {
  13. instance = new RetrofitClient();
  14. }
  15. return instance;
  16. }
  17. public Api getApi() {
  18. return mRetrofit.create(Api.class);
  19. }
  20. }

PositionalUserDataSource

针对于 PositionalDataSource,来看我们的代码:

  1. public class PositionalUserDataSource extends PositionalDataSource<UserInfo> {
  2. public static final int PAGE_SIZE = 20 ;
  3. /**
  4. * 首次加载数据
  5. */
  6. @Override
  7. public void loadInitial(@NonNull LoadInitialParams params,
  8. @NonNull final LoadInitialCallback<UserInfo> callback) {
  9. final int startPosition = 0;
  10. RetrofitClient.
  11. getInstance().
  12. getApi().
  13. getUserListByPositional(startPosition,PAGE_SIZE).
  14. enqueue(new Callback<UserModel>() {
  15. @Override
  16. public void onResponse(@NonNull Call<UserModel> call,
  17. @NonNull Response<UserModel> response) {
  18. if(response.isSuccessful()) {
  19. UserModel body = response.body();
  20. if(null != body) {
  21. callback.onResult(body.data.userList,startPosition, body.data.total);
  22. }
  23. }
  24. }
  25. @Override
  26. public void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {
  27. }
  28. });
  29. }
  30. /**
  31. *
  32. * 第 N 页加载数据
  33. *
  34. * @param params
  35. * @param callback
  36. */
  37. @Override
  38. public void loadRange(@NonNull LoadRangeParams params,
  39. @NonNull final LoadRangeCallback<UserInfo> callback) {
  40. RetrofitClient.
  41. getInstance().
  42. getApi().
  43. getUserListByPositional(params.startPosition,PAGE_SIZE).
  44. enqueue(new Callback<UserModel>() {
  45. @Override
  46. public void onResponse(@NonNull Call<UserModel> call, @NonNull Response<UserModel> response) {
  47. if(response.isSuccessful() && null != response.body()){
  48. callback.onResult(response.body().data.userList);
  49. }
  50. }
  51. @Override
  52. public void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {}
  53. });
  54. }
  55. }

有两个抽象方法需要我们重写:loadInitialloadRange方法:

其中 loadInitial 代表加载第一页的方法,但是需要注意的是 LoadInitialCallback.onResult方法:

是要告之请求的List的总长度,如果我们在 PagedList.Config中设置了 setEnablePlaceHolders() 方法为true,那么此处我们就应该设置List的totalCount,否则我们大程序就会报错。

loadRange 方法可以理解为加载下一页的动作,数据加载完成之后,仍然在 LoadInitialCallback.onResult中显示结果。

PagedKeyUserDataSource
同理,PagedKeyUserDataSource继承自抽象类PageKeyedDataSource:
 

  1. public class PagedKeyUserDataSource extends PageKeyedDataSource<Integer, UserInfo> {
  2. public static final int FIRST_PAGE = 1 ;
  3. public static final int PAGE_SIZE = 20 ;
  4. /**
  5. * 加载第一页数据
  6. *
  7. * @param params
  8. * @param callback
  9. */
  10. @Override
  11. public void loadInitial(@NonNull LoadInitialParams<Integer> params,
  12. @NonNull final LoadInitialCallback<Integer, UserInfo> callback) {
  13. RetrofitClient.getInstance().getApi().
  14. getUserListByPageSize(FIRST_PAGE,FIRST_PAGE).
  15. enqueue(new Callback<UserModel>() {
  16. @Override
  17. public void onResponse(@NonNull Call<UserModel> call,
  18. @NonNull Response<UserModel> response) {
  19. if(response.isSuccessful() && null != response.body()){
  20. callback.onResult(response.body().data.userList,
  21. null,
  22. FIRST_PAGE + 1);
  23. }
  24. }
  25. @Override
  26. public void onFailure(Call<UserModel> call, Throwable t) {
  27. }
  28. });
  29. }
  30. /**
  31. * 加载下一页的数据
  32. *
  33. * @param params
  34. * @param callback
  35. */
  36. @Override
  37. public void loadAfter(@NonNull final LoadParams<Integer> params,
  38. @NonNull final LoadCallback<Integer, UserInfo> callback) {
  39. RetrofitClient.getInstance().getApi().
  40. getUserListByPageSize(params.key, PAGE_SIZE).
  41. enqueue(new Callback<UserModel>() {
  42. @Override
  43. public void onResponse(@NonNull Call<UserModel> call, @NonNull Response<UserModel> response) {
  44. if(response.isSuccessful() && null != response.body()){
  45. UserModel.UserList userList = response.body().data;
  46. boolean hasMoreData = userList != null && userList.userList.size() >= PAGE_SIZE;
  47. callback.onResult(userList.userList, hasMoreData ? params.key + 1 : null);
  48. }
  49. }
  50. @Override
  51. public void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {
  52. }
  53. });
  54. }
  55. @Override
  56. public void loadBefore(@NonNull LoadParams<Integer> params,
  57. @NonNull LoadCallback<Integer, UserInfo> callback) {
  58. }
  59. }

 

同理它也有三个抽象方法需要重写:loadInitial、loadAfter、loadBefore方法,意义与 PositionalDataSource 中方法意义类型,都是加载第一页和第N页的逻辑,对于新增的 loadBefore代表在加载之前做的事情,个人现在用不到,感觉没什么太大的意义。

ItemKeyedUserDataSource
ItemKeyedUserDataSource也是继承自ItemKeyedDataSource抽象类,代码如下:
 

  1. public class ItemKeyedUserDataSource extends ItemKeyedDataSource<Integer, UserInfo> {
  2. public static final int PAGE_SIZE = 20 ;
  3. @Override
  4. public void loadInitial(@NonNull LoadInitialParams<Integer> params,
  5. @NonNull final LoadInitialCallback<UserInfo> callback) {
  6. RetrofitClient.
  7. getInstance().
  8. getApi().
  9. getUserListByItemPaged(0, PAGE_SIZE).
  10. enqueue(new Callback<UserModel>() {
  11. @Override
  12. public void onResponse(@NonNull Call<UserModel> call, @NonNull Response<UserModel> response) {
  13. if(response.isSuccessful() && null != response.body()) {
  14. callback.onResult(response.body().data.userList);
  15. }
  16. }
  17. @Override
  18. public void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {
  19. }
  20. });
  21. }
  22. @Override
  23. public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull final LoadCallback<UserInfo> callback) {
  24. RetrofitClient.getInstance().
  25. getApi().
  26. getUserListByItemPaged(params.key, PAGE_SIZE).
  27. enqueue(new Callback<UserModel>() {
  28. @Override
  29. public void onResponse(@NonNull Call<UserModel> call, @NonNull Response<UserModel> response) {
  30. if(response.isSuccessful() && null != response.body()) {
  31. callback.onResult(response.body().data.userList);
  32. }
  33. }
  34. @Override
  35. public void onFailure(@NonNull Call<UserModel> call, @NonNull Throwable t) {
  36. }
  37. });
  38. }
  39. @Override
  40. public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<UserInfo> callback) {}
  41. @NonNull
  42. @Override
  43. public Integer getKey(@NonNull UserInfo item) {
  44. return item.userId;
  45. }

它有四个抽象方法需要重写:loadInitial、loadAfter、loadBefore、getKey。前三个方法和前面的意义类似,这里就不说了;对于getKey方法,就是我们获取下一页nextId的地方,也比较简单。

对于我们的 三种 DataSource获取完成之后,来看一下我们如果将数据展示在 RecyclerView 上了:

首先定义一个我们的SourceFactory:
 

  1. public class UserDataSourceFactory extends DataSource.Factory<Integer, UserInfo> {
  2. // 这里可以根据需求换成另外两种DataSource即可。
  3. private MutableLiveData<PositionalUserDataSource> liveDataSource = new MutableLiveData<>();
  4. @NonNull
  5. @Override
  6. public DataSource<Integer, UserInfo> create() {
  7. PositionalUserDataSource source = new PositionalUserDataSource();
  8. liveDataSource.postValue(source);
  9. return source;
  10. }
  11. }

然后定义一下我们的ViewModel

  1. public class UserViewModel extends ViewModel {
  2. public LiveData<PagedList<UserInfo>> userPagedList;
  3. public UserViewModel() {
  4. PagedList.Config config = new PagedList.Config.Builder().
  5. // 用于控件占位
  6. setEnablePlaceholders(true).
  7. // 设置每页的大小
  8. setPageSize(PositionalUserDataSource.PAGE_SIZE).
  9. // 设置当距离底部还有多少条数据时开始加载下一页
  10. setPrefetchDistance(3).
  11. // 设置首次加载数据的数量 默认为 page_size 的三倍
  12. setInitialLoadSizeHint(PositionalUserDataSource.PAGE_SIZE * 3).
  13. // 设置pagedList 所能承受的最大数量
  14. setMaxSize(65536 * PositionalUserDataSource.PAGE_SIZE).
  15. build();
  16. userPagedList = new LivePagedListBuilder<>(new UserDataSourceFactory(),config).build();
  17. }
  18. }

设置并定义下PagedList.Config,其中几个比较重要的方法含义已经贴上去了。

最后,贴一下Adapter,一般使用Paging组件的话,都会使用androidx.paging.PagedListAdapter:

  1. public class UserPagedListAdapter extends PagedListAdapter<UserInfo, UserPagedListAdapter.UserItemViewHolder> {
  2. private Context mContext;
  3. public UserPagedListAdapter(Context context) {
  4. super(DIFF_CALLBACK);
  5. this.mContext = context;
  6. }
  7. private static DiffUtil.ItemCallback<UserInfo> DIFF_CALLBACK = new DiffUtil.ItemCallback<UserInfo>() {
  8. @Override
  9. public boolean areItemsTheSame(@NonNull UserInfo oldItem, @NonNull UserInfo newItem) {
  10. return oldItem.userId == newItem.userId;
  11. }
  12. @Override
  13. public boolean areContentsTheSame(@NonNull UserInfo oldItem, @NonNull UserInfo newItem) {
  14. return oldItem.userName.equals(newItem.userName) &&
  15. oldItem.userAvatar.equals(newItem.userAvatar) ;
  16. }
  17. };
  18. @NonNull
  19. @Override
  20. public UserItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
  21. return new UserItemViewHolder(LayoutInflater.from(mContext).
  22. inflate(R.layout.positional_user_item_layout,parent,false));
  23. }
  24. @Override
  25. public void onBindViewHolder(@NonNull UserItemViewHolder holder, int position) {
  26. //TODO
  27. }
  28. static class UserItemViewHolder extends RecyclerView.ViewHolder {
  29. public UserItemViewHolder(@NonNull View itemView) {
  30. super(itemView);
  31. }
  32. }
  33. }

最后在Activity中:

  1. public class MainActivity extends AppCompatActivity {
  2. private RecyclerView mRecyclerView;
  3. private UserPagedListAdapter mPositionalAdapter;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.activity_main);
  8. mRecyclerView = findViewById(R.id.id_recycler_view);
  9. initPositionalAdapter();
  10. initPositionalObserve();
  11. }
  12. private void initPositionalAdapter() {
  13. mPositionalAdapter = new UserPagedListAdapter(this);
  14. mRecyclerView.setAdapter(mPositionalAdapter);
  15. mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
  16. mRecyclerView.setHasFixedSize(true);
  17. }
  18. private void initPositionalObserve() {
  19. UserViewModel viewModel = new ViewModelProvider(this).
  20. get(UserViewModel.class);
  21. viewModel.userPagedList.observe(this, new Observer<PagedList<UserInfo>>() {
  22. @Override
  23. public void onChanged(PagedList<UserInfo> userInfoPagedList) {
  24. mPositionalAdapter.submitList(userInfoPagedList);
  25. }
  26. });
  27. }
  28. }


————————————————
版权声明:本文为CSDN博主「microhex」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013762572/article/details/107452801

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/286516
推荐阅读
相关标签
  

闽ICP备14008679号