Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目录

Paging

官方文档

官方案例

简介

Paging 是什么?

Paging 可以使开发者更轻松在 RecyclerView分页加载数据

  1. implementation "android.arch.paging:runtime:1.0.1" //Paging
  2. implementation "android.arch.paging:rxjava2:1.0.1" //Paging对RxJava2的支持

原理示意图:https://upload-images.jianshu.io/upload_images/7293029-27facf0a399c66b8.gif?imageMogr2/auto-orient/

组成部分:

  • DataSource:数据源,数据的改变会驱动列表的更新,因此,数据源是很重要的
  • PageList:核心类,它从数据源取出数据,同时,它负责控制第一次默认加载多少数据,之后每一次加载多少数据,如何加载等等,并将数据的变更反映到UI上。
  • PagedListAdapter:适配器,RecyclerView的适配器,通过分析数据是否发生了改变,负责处理UI展示的逻辑(增加/删除/替换等)。

使用步骤

创建数据源

在Paging中,数据源被抽象为 DataSource , 其获取需要依靠 DataSource 的内部工厂类 DataSource.Factory ,通过create()方法就可以获得DataSource 的实例:

  1. public abstract static class Factory<Key, Value> {
  2. public abstract DataSource<Key, Value> create();
  3. }

数据源一般有两种选择,远程服务器请求或者读取本地持久化数据,这些并不重要,本文我们以Room数据库为例:

  1. @Query("SELECT * FROM table_user")
  2. DataSource.Factory<Integer, User> getAllUserDataSource();
  1. DataSource.Factory<Integer, User> factory = UserDb.get(getApplication()).userDao().getAllUserDataSource();

Paging可以获得Room的原生支持,因此作为示例非常合适,当然我们更多获取数据源是通过API网络请求,其实现方式可以参考 官方Sample

PS:如果通过API网络请求获取DataSource,相比使用Room来说要麻烦很多

配置PageList

PageList的作用:

  • 从数据源取出数据
  • 负责控制第一次默认加载多少数据,之后每一次加载多少数据,如何加载等等
  • 将数据的变更反映到UI上

PageList提供了 PagedList.Config 类供我们进行实例化配置,其提供了5个可选配置:

  1. public static final class Builder {
  2. // 省略Builder其他内部方法
  3. private int mPageSize = -1; //每次加载多少数据
  4. private int mPrefetchDistance = -1; //距底部还有几条数据时,加载下一页数据
  5. private int mInitialLoadSizeHint = -1; //第一次加载多少数据,必须是分页加载数量的倍数
  6. private boolean mEnablePlaceholders = true; //是否启用占位符,若为true,则视为固定数量的item
  7. private int mMaxSize = MAX_SIZE_UNBOUNDED; //默认Integer.MAX_VALUE,Defines how many items to keep loaded at once.
  8. }

配置Adapter

就像我们平时配置 RecyclerView 差不多,我们配置了 ViewHolder 和 RecyclerView.Adapter,略微不同的是,我们需要继承PagedListAdapter,并且我们需要传一个 DifffUtil.ItemCallback 的实例。

DifffUtil.ItemCallback的意义是,我需要知道怎么样的比较,才意味着数据源的变化,并根据变化再进行的UI刷新操作。

监听数据源的变更,并响应在UI上

这个就很简单了

  1. //每当观察到数据源中数据的变化,我们就把最新的数据交给Adapter去展示
  2. viewModel.getRefreshLiveData().observe(this, pagedList -> {
  3. Log.i("bqt", "【数据发生改变】" + pagedList.size() + " "
  4. + pagedList.getPositionOffset() + " " + pagedList.getLoadedCount() + " " + pagedList.getLastKey() + " "
  5. + pagedList.isImmutable() + " " + pagedList.isDetached());
  6. adapter.submitList(pagedList); //将数据的变化反映到UI上 Set the new list to be displayed
  7. });

PageKeyedDataSource

参考

基本结构:

  1. //这个数据源主要需要传递Int型的PageNum作为参数实现每一页数据的请求
  2. public class PagingDataSource extends PageKeyedDataSource<Integer, User> {
  3. @Override
  4. public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, User> callback) {
  5. //requestedLoadSize为加载的数据量,placeholdersEnabled是是否显示占位;callback为数据加载完成的回调
  6. //LoadInitialCallback的onResult方法有三个参数,第一个为数据,后面两个即为上一页和下一页
  7. Log.i("bqt", "【loadInitial】" + params.requestedLoadSize + " " + params.placeholdersEnabled);//初始加载数据
  8. //if(满足条件) 请求一批数据,数据处理后通过callback返回
  9. //callback.onResult(List<Value> data, Key previousPageKey, Key nextPageKey);
  10. }
  11. @Override
  12. public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, User> callback) {
  13. Log.i("bqt", "【loadBefore】" + params.key + " " + params.requestedLoadSize);//向前分页加载数据
  14. //key即为DataSource<Key, Value>中的key,在这里即为页数;同样,callback为数据加载完成的回调
  15. //LoadParams中的key即为我们要加载页的数据,加载完后回调中告知下一次加载数据页数+1或者-1
  16. }
  17. @Override
  18. public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, User> callback) {
  19. Log.i("bqt", "【loadAfter】" + params.key + " " + params.requestedLoadSize);//向后分页加载数据
  20. //if(满足条件) 再请求一批数据,数据处理后通过callback返回
  21. //callback.onResult(List<Value> data, Key adjacentPageKey);
  22. }
  23. }

继承自PageKeyedDataSource后需要实现以下三个方法:

  • loadInitial 初始加载数据
  • loadAfter 向后分页加载数据
  • loadBefore 向前分页加载数据

这三个方法都有两个参数,一个params和一个callback

  • params包装了分页加载的参数:

    • loadInitial中的params为LoadInitialParams包含了requestedLoadSize和placeholdersEnabled两个属性,requestedLoadSize为加载的数据量,placeholdersEnabled是是否显示占位及当数据为null时显示占位的view
    • loadBefore和loadAfter中的params为LoadParams包含了key和requestedLoadSize,key即为DataSource<Key, Value>中的key,在这里即为页数
  • callback为数据加载完成的回调,loadInitial中调用调用IPVTApiPresenter加载数据,然后调用callback.onResult告诉调用者数据加载完成。

onResult有三个参数,第一个为数据,后面两个即为上一页和下一页。

如果我们当前页为第一页即没有上一页,则上一页为null,下一页为2,此时加载的时候会加载当前页和调用loadAfter加载第二页,但不会调用loadBefore,因为没有上一页,即previousPageKey为null不会加载上一页

如果我们初始加载的是第三页,则上一页是2,下一页是4,此时加载的时候会加载当前页和调用loadAfter加载第4页,调用loadBefore加载第二页

分页加载的时候会将previousPageKey或nextPageKey传递到loadAfter或loadBefore中的params.key

loadAfter 、loadBefore中的params中的key即为我们要加载页的数据,加载完后回调中告知下一次加载数据页数+1或者-1

Java版案例

参考 此博客,原文为Kotlin版案例,我将其转为Java版实现,并在其基础上添加了一些逻辑。

PagingActivity

  1. public class PagingActivity extends AppCompatActivity {
  2. @Override
  3. protected void onCreate(@Nullable Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_paging);
  6. RecyclerView recyclerView = findViewById(R.id.recyclerView);
  7. DiffUtil.ItemCallback<User> itemCallback = new DiffUtil.ItemCallback<User>() {
  8. @Override
  9. public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) {
  10. return oldItem.uid == newItem.uid;
  11. }
  12. @Override
  13. public boolean areContentsTheSame(@NonNull User oldItem, @NonNull User newItem) {
  14. return oldItem == newItem;
  15. }
  16. };
  17. PagingAdapter adapter = new PagingAdapter(itemCallback);
  18. UserDao dao = UserDb.get(this).userDao();
  19. adapter.setOnClick((user, position) -> {
  20. Log.i("bqt", "【position】" + position);
  21. new Thread(() -> {
  22. if (position % 2 == 0) dao.deleteUser(user);
  23. else dao.insertUser(new User("insert"));
  24. }).start();
  25. });
  26. recyclerView.setAdapter(adapter);
  27. recyclerView.setLayoutManager(new LinearLayoutManager(this));
  28. dao.getAllUser().observe(this, users -> Log.i("bqt", "【数据发生改变】" + users.size()));
  29. PagingViewModel viewModel = ViewModelProviders.of(this).get(PagingViewModel.class);
  30. //每当观察到数据源中数据的变化,我们就把最新的数据交给Adapter去展示
  31. viewModel.getRefreshLiveData().observe(this, pagedList -> {
  32. Log.i("bqt", "【数据发生改变】" + pagedList.size() + " "
  33. + pagedList.getPositionOffset() + " " + pagedList.getLoadedCount() + " " + pagedList.getLastKey() + " "
  34. + pagedList.isImmutable() + " " + pagedList.isDetached());
  35. adapter.submitList(pagedList); //将数据的变化反映到UI上 Set the new list to be displayed
  36. });
  37. }
  38. }

PagingAdapter

  1. public class PagingAdapter extends PagedListAdapter<User, PagingAdapter.MyViewHolder> {
  2. PagingAdapter(DiffUtil.ItemCallback<User> itemCallback) {
  3. super(itemCallback);
  4. }
  5. @Override
  6. public void onCurrentListChanged(@Nullable PagedList<User> previousList, @Nullable PagedList<User> currentList) {
  7. super.onCurrentListChanged(previousList, currentList);
  8. Log.i("bqt", "【onCurrentListChanged】");
  9. }
  10. @NonNull
  11. @Override
  12. public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
  13. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_student, parent, false);
  14. return new MyViewHolder(view);
  15. }
  16. @Override
  17. public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
  18. Log.i("bqt", "【onBindViewHolder】" + position);
  19. User user = getItem(position);
  20. //items might be null if they are not paged in yet. PagedListAdapter will re-bind the ViewHolder when Item is loaded.
  21. if (user != null) {
  22. holder.nameView.setText(user.name);
  23. holder.nameView.setOnClickListener(v -> {
  24. if (onClick != null) {
  25. onClick.onClick(user, position);
  26. }
  27. });
  28. }
  29. }
  30. class MyViewHolder extends RecyclerView.ViewHolder {
  31. TextView nameView;
  32. MyViewHolder(View view) {
  33. super(view);
  34. nameView = view.findViewById(R.id.name);
  35. }
  36. }
  37. private OnClick onClick;
  38. void setOnClick(OnClick onClick) {
  39. this.onClick = onClick;
  40. }
  41. interface OnClick {
  42. void onClick(User user, int position);
  43. }
  44. }

PagingViewModel

  1. public class PagingViewModel extends AndroidViewModel {
  2. public PagingViewModel(@NonNull Application application) {
  3. super(application);
  4. }
  5. public LiveData<PagedList<User>> getRefreshLiveData() {
  6. DataSource.Factory<Integer, User> dataSourceFactory = UserDb.get(getApplication()).userDao().getAllUserDataSource();
  7. PagedList.Config config = new PagedList.Config.Builder()
  8. .setInitialLoadSizeHint(10) //第一次加载多少数据,必须是分页加载数量的倍数
  9. .setPageSize(5) //每次加载多少数据
  10. .setMaxSize(Integer.MAX_VALUE) //Defines how many items to keep loaded at once.
  11. .setPrefetchDistance(5) //距底部还有几条数据时,加载下一页数据
  12. .setEnablePlaceholders(true) //是否启用占位符,若为true,则视为固定数量的item
  13. .build();
  14. LivePagedListBuilder<Integer, User> livePagedListBuilder = new LivePagedListBuilder<>(dataSourceFactory, config)
  15. .setFetchExecutor(Executors.newSingleThreadExecutor()) //设置获取数据源的线程
  16. .setInitialLoadKey(0) //可通过 pagedList.getLastKey() 获取此值,默认值当然为 Key(这里为Integer)类型的初始化值()这里为0
  17. .setBoundaryCallback(new PagedList.BoundaryCallback<User>() {
  18. @Override
  19. public void onZeroItemsLoaded() { //没有数据被加载
  20. super.onZeroItemsLoaded();
  21. Log.i("bqt", "【onZeroItemsLoaded】");
  22. }
  23. @Override
  24. public void onItemAtFrontLoaded(@NonNull User itemAtFront) { //加载第一个
  25. super.onItemAtFrontLoaded(itemAtFront);
  26. Log.i("bqt", "【onItemAtFrontLoaded】" + itemAtFront.name);
  27. }
  28. @Override
  29. public void onItemAtEndLoaded(@NonNull User itemAtEnd) { //加载最后一个
  30. super.onItemAtEndLoaded(itemAtEnd);
  31. Log.i("bqt", "【onItemAtEndLoaded】" + itemAtEnd.name);
  32. }
  33. });
  34. return livePagedListBuilder.build();
  35. }
  36. }

User

  1. @Entity(tableName = "table_user")
  2. public class User {
  3. @PrimaryKey(autoGenerate = true) public int uid;
  4. @ColumnInfo(name = "user_name") public String name = "包青天";
  5. public User(String name) {
  6. this.name = name;
  7. }
  8. }

UserDao

  1. @Dao
  2. public interface UserDao {
  3. @Insert
  4. List<Long> insertUser(User... users);
  5. @Insert
  6. List<Long> insertUser(List<User> users);
  7. @Delete
  8. int deleteUser(User user);
  9. @Query("SELECT * FROM table_user")
  10. LiveData<List<User>> getAllUser();
  11. @Query("SELECT * FROM table_user")
  12. DataSource.Factory<Integer, User> getAllUserDataSource();
  13. }

UserDb

  1. @Database(entities = {User.class}, version = 1)
  2. public abstract class UserDb extends RoomDatabase {
  3. public abstract UserDao userDao(); //没有参数的抽象方法,返回值所代表的类必须用@Dao注解
  4. private static UserDb db;
  5. public static UserDb get(Context context) {
  6. if (db == null) {
  7. db = Room.databaseBuilder(context.getApplicationContext(), UserDb.class, "dbname")
  8. .addCallback(new RoomDatabase.Callback() {
  9. @Override
  10. public void onCreate(@NonNull SupportSQLiteDatabase database) {
  11. super.onCreate(database);
  12. Log.i("bqt", "【onCreate】");
  13. new Thread(() -> {
  14. List<User> users = new ArrayList<>();
  15. for (int i = 0; i < 50; i++) {
  16. users.add(new User("bqt" + i));
  17. }
  18. get(context).userDao().insertUser(users);
  19. }).start();
  20. }
  21. })
  22. .build();
  23. }
  24. return db;
  25. }
  26. }

2019-4-7

附件列表

Jetpack 架构组件 Paging 分页加载 MD的更多相关文章

  1. Jetpack系列:Paging组件帮你解决分页加载实现的痛苦

    相信很多小伙伴们在项目实战中,经常会用到界面的分页显示.加载更多等功能.需要针对具体功能做针对性开发和调试,耗时耗力. Paging组件的使用将这部分的工作简化,从而让开发者更专注于业务的具体实现.下 ...

  2. Android Jetpack 架构组件最佳实践之“网抑云”APP

    背景 近几年,Android 相关的新技术层出不穷.往往这个技术还没学完,下一个新技术又出来了.很多人都是一脸黑人问号? 不少开发者甚至开始哀嚎:"求求你们别再创造新技术了,我们学不动了!& ...

  3. Jetpack架构组件学习(4)——APP Startup库的使用

    最近在研究APP的启动优化,也是发现了Jetpack中的App Startup库,可以进行SDK的初始化操作,于是便是学习了,特此记录 原文:Jetpack架构组件学习(4)--App Startup ...

  4. java攻城狮之路(Android篇)--widget_webview_metadata_popupwindow_tabhost_分页加载数据_菜单

    一.widget:桌面小控件1 写一个类extends AppWidgetProvider 2 在清单文件件中注册: <receiver android:name=".ExampleA ...

  5. [转]微信小程序之加载更多(分页加载)实例 —— 微信小程序实战系列(2)

    本文转自;http://blog.csdn.net/michael_ouyang/article/details/56846185 loadmore 加载更多(分页加载) 当用户打开一个页面时,假设后 ...

  6. ListView实现分页加载(三)实现分页加载

    在上一篇中,我们实现了底部布局(即带上了进度条).没有读过的朋友可以点击下面的链接: http://www.cnblogs.com/fuly550871915/p/4866966.html 但是进度条 ...

  7. ListView实现分页加载(一)制作Demo

    一.什么是分页加载 在下面的文章中,我们来讲解LitView分页加载的实现.什么是分页加载呢?我们先看几张效果图吧,如下:                                       ...

  8. android中滑动SQLite数据库分页加载

    今天用到了android中滑动SQlit数据库分页加载技术,写了个测试工程,将代码贴出来和大家交流一下: MainActivity package com.example.testscrollsqli ...

  9. Jetpack架构组件学习(2)——ViewModel和Livedata使用

    要看本系列其他文章,可访问此链接Jetpack架构学习 | Stars-One的杂货小窝 原文地址:Jetpack架构组件学习(2)--ViewModel和Livedata使用 | Stars-One ...

随机推荐

  1. Codeforces 1137D Cooperative Game (看题解)

    Cooperative Game 智商题, 感觉不太能推出来, 虽然看看证明过程是对的. #include<bits/stdc++.h> #define LL long long #def ...

  2. asp.net core 使用docker默认端口修改

    默认端口是80 在dockerfile文件中修改 ENV ASPNETCORE_URLS http://+:80 ------------------------------------------- ...

  3. Python 扫盲

    yield使用浅析 菜鸟教程:http://www.runoob.com/w3cnote/python-yield-used-analysis.html #!/usr/bin/python # -*- ...

  4. HDU 5178 pairs【二分】||【尺取】

    <题目链接> 题目大意: 给定一个整数序列,求出绝对值小于等于k的有序对个数. 解题分析: $O(nlong(n))$的二分很好写,这里就不解释了.本题尺取$O(n)$也能做,并且效率很不 ...

  5. Hystrix快速入门

    祝大家国庆快乐! 对大部分电商和快递公司来说,每年年底(Q4季度)由于双11等大促活动的存在,将面对大量的用户流量,尤其是属于大促的那几天,无论是用户的商品订单还是物流订单,都将是平时的3倍以上.对于 ...

  6. xss小结-从xss平台搭建到csp规则

    0x00前言 xss是跨站脚本攻击,利用嵌入js代码达到‘控制’对方浏览器的作用,测试的时候我们是用alert(1)弹窗,而做CTF也好,实际中的漏洞利用也好一般是用xss获取管理员的cookie 0 ...

  7. 自定义sshd服务

    1.安装rsyslog服务和sshd服务并启动 2.配置日志文件    vim /etc/rsyslog.conf        在里面添加一行 local*.    /var/log/sshd.lo ...

  8. Java并发编程(十三)-- 线程池

    什么是线程池? 线程池就是以一个或多个线程循环执行多个应用逻辑的线程集合. 为什么用线程池? 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率 例如: 记创建线程消耗时 ...

  9. 屏幕录制软件camtasia studio 8序列号激活

    注册名:TEAM MESMERiZE序列号:3MHCA-5DMCV-H89T8-V8GML-W6FB8 打开hosts文件:C:\Windows\System32\drivers\etc\hosts把 ...

  10. ES6 迭代器和生成器

    设计为了更高效的数据处理,避免过多for循环嵌套(代码复杂度,跟踪多个循环变量) 1. 迭代器: 为迭代过程设计的接口 所有的迭代器对象都有next()方法,每次调用都返回一个结果对象,对象有两个属性 ...