赞
踩
在此之前,我一直对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
大概的返回结果我们也可以定义一下:
- {
- "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);
- }
- }
-
PositionalUserDataSource
针对于 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
同理,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
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);
- }
- });
- }
- }
-
————————————————
版权声明:本文为CSDN博主「microhex」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013762572/article/details/107452801
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。