Jetpack系列:Paging组件帮你解决分页加载实现的痛苦
相信很多小伙伴们在项目实战中,经常会用到界面的分页显示、加载更多等功能。需要针对具体功能做针对性开发和调试,耗时耗力。
Paging组件的使用将这部分的工作简化,从而让开发者更专注于业务的具体实现。下面我们一起来学习下Paging组件的使用方法。
首先来看下使用Paging组件实现的分页加载和刷新效果:

数据库读取分页加载

网络端分页请求数据
下面我们针对这两个使用Paging组件的例子进行分析。
- 数据库读取分页加载示例中,数据一次性获取完成,界面分页显示,按需加载数据,减少了内存资源的使用
- 网络端分页请求数据,每次请求固定长度的数据信息进行显示,减少网络带宽的使用
Paging功能的实现用到了Room组件,Room也是Jetpack库的一部分,在SQLite上提供了一个抽象层,为开发者提供了流畅的SQLite数据库访问体验。
Room简介
Room组件包含三个主要组成部分:
- 数据库
其应该满足四个条件:
- 含有@Database注解
- 是一个继承自RoomDatabase的抽象类
- 注解内包含实体的列表信息
- 包含一个返回带@Dao注解类的无参方法
- 数据实体
表示数据库中表
- DAO
包含用于访问数据库的方法
应用程序使用Room组件获取与数据库关联的数据访问对象或DAO,然后获取实体,将实体的所有更改同步到数据库。Room三个部分之间的关系如下图:

Room架构图(引自官方文档)
Paging的基本使用方法
Paging组件支持三种不同数据结构:
- 仅从网络获取
- 仅从设备数据库获取
- 两种数据来源的组合,使用设备数据库作为缓存

分页库支持数据架构(引自官方文档)
下面我们以仅从设备数据库获取的方式来了解下Paging分页的基本使用方法。
环境配置
首先需要在模块build.gradle中添加对应库支持。
dependencies {
versions.room = "2.1.0-alpha06"
versions.lifecycle = "2.2.0-alpha03"
versions.paging = "2.1.0-rc01"
//room数据库访问依赖
implementation "androidx.room:room-runtime:$versions.room"
//lifecycle组件依赖,ViewModel
implementation "androidx.lifecycle:lifecycle-runtime:$versions.lifecycle"
//paging组件依赖
implementation "androidx.paging:paging-runtime-ktx:$versions.paging"
kapt "androidx.room:room-compiler:$versions.room"
}
布局文件
界面的布局比较简单,主界面包含一个输入框,一个按钮和一个RecyclerView,列表每一项的显示采用卡片式布局,显示文本。
<androidx.cardview.widget.CardView ...>
<TextView android:id="@+id/name" .../>
</androidx.cardview.widget.CardView>
数据准备
在主Activity进行数据获取和显示前,需要做几点准备工作:
- 创建数据实体类Cheese
- 创建数据库方法DAO
- 创建数据库CheeseDb
- 创建自定义CheeseViewModel
1. 创建实体Cheese
实体代表了数据库每条数据对象,需要注意必须加@Entity注解
@Entity
data class Cheese(@PrimaryKey(autoGenerate = true) val id: Int, val name: String)
此声明创建了一个数据库实体,字段有ID和Name,主键为ID
2. 创建数据库操作方法DAO
数据库方法提供了对数据库的基本操作,必须加@Dao注解
@Dao
interface CheeseDao {
@Query("SELECT * FROM Cheese ORDER BY name COLLATE NOCASE ASC")
fun allCheesesByName(): DataSource.Factory<Int, Cheese>
@Insert
fun insert(cheeses: List<Cheese>)
@Insert
fun insert(cheese: Cheese)
@Delete
fun delete(cheese: Cheese)
}
此处提供了针对数据库的查询,插入和删除方法,可以看到在查询方法里面会指定数据源类型,当前使用默认类型。Paging还支持如下三种数据源:
- PageKeyedDataSource
实现按上下页加载显示
- ItemKeyedDataSource
根据上一条数据获取下一条数据
- PositionalDataSource
从指定位置开始加载
3. 创建数据库
数据库为界面显示提供了数据支持,当前示例程序中,数据库创建时,插入了预置数据。
必须加@Database注解
必须声明数据列表信息
必须含有无参抽象方法,返回带@Dao注解的类
必须为抽象类,且继承RoomDatabase
@Database(entities = arrayOf(Cheese::class), version = 1)
abstract class CheeseDb : RoomDatabase() {
abstract fun cheeseDao(): CheeseDao//返回DAO
...
//获取数据库实例,同步且单例
@Synchronized
fun get(context: Context): CheeseDb {
if (instance == null) {
instance = Room.databaseBuilder(context.applicationContext,
CheeseDb::class.java, "CheeseDatabase")
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
//数据库创建时插入预置数据
fillInDb(context.applicationContext)
}
}).build()
}
return instance!!
}
private fun fillInDb(context: Context) {
// inserts in Room are executed on the current thread, so we insert in the background
// CHEESE_DATA为默认数据列表
ioThread {
get(context).cheeseDao().insert(
CHEESE_DATA.map { Cheese(id = 0, name = it) })
}
}
}
4. 创建ViewModel
创建自定义ViewModel为界面和数据提供处理支持。其包含了DAO,数据列表信息等。
class CheeseViewModel(app: Application) : AndroidViewModel(app) {
val dao = CheeseDb.get(app).cheeseDao()
val allCheeses = dao.allCheesesByName().toLiveData(Config(
pageSize = 30,//指定页面显示的数据项数量
enablePlaceholders = true,//是否允许使用占位符
maxSize = 200 //一次性加载数据的最大数量
),
fetchExecutor = Executor { }//自定义Executor更好地控制paging库何时从应用程序的数据库中加载列表
)
fun insert(text: CharSequence) = ioThread {
dao.insert(Cheese(id = 0, name = text.toString()))
}
fun remove(cheese: Cheese) = ioThread {
dao.delete(cheese)
}
}
小提示:自定义ViewModel直接继承AndroidViewModel,可以在其中做一些依赖于Context的资源获取等功能。
public class AndroidViewModel extends ViewModel {
...
public <T extends Application> T getApplication() {
return (T) mApplication;
}
}
ViewModel的创建,包含了数据的获取和更新:
- 通过DAO获取数据库的数据列表
- 使用LiveData组件管理数据
- 增加分页支持(pageSize,enablePlaceholders,maxSize)功能
- 增加自定义Executor
Paging组件是依赖页面长度、占位符、最大长度三个属性来进行小块数据加载显示的。
页面大小:每页显示的实体数量
最大长度:也称预取长度,此值应为pageSize的几倍大小(具体项目可根据实际情况调试)
占位符:如果设置为true,则为尚未完成加载的列表项显示占位符
占位符的使用需要有可数的数据集合,默认显示效果,数据项有相同大小的视图显示,有以下优点:
- 提供完整滚动条支持
- 无需显示加载更多项
界面绑定
数据已经准备好了,下面开始和界面进行绑定显示。
界面显示时,需要提供与RecyclerView绑定的adapter,需要注意使用Paging进行分页加载,adapter需要继承自PagedListAdapter。
class CheeseAdapter : PagedListAdapter<Cheese, CheeseViewHolder>(diffCallback) {
override fun onBindViewHolder(holder: CheeseViewHolder, position: Int) {
holder.bindTo(getItem(position))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CheeseViewHolder =
CheeseViewHolder(parent)
companion object {
//根据diffCallback来确认新加载的数据是否与旧数据有差异,确定是否更新显示
private val diffCallback = object : DiffUtil.ItemCallback<Cheese>() {
override fun areItemsTheSame(oldItem: Cheese, newItem: Cheese): Boolean =
oldItem.id == newItem.id
//kotlin使用==会将对象的内容进行对比,使用java需要重写equals方法并替换
override fun areContentsTheSame(oldItem: Cheese, newItem: Cheese): Boolean =
oldItem == newItem
}
}
}
//ViewHolder的实现比较简单,将Cheese数据更新到TextView
class CheeseViewHolder(parent :ViewGroup) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.cheese_item, parent, false)) {
private val nameView = itemView.findViewById<TextView>(R.id.name)
var cheese : Cheese? = null
//未绑定数据,或者打开占位符后快速滑动会出现cheese为null,实际项目中需要
//处理此种情况,数据加载时会重新rebind
fun bindTo(cheese : Cheese?) {
this.cheese = cheese
nameView.text = cheese?.name
}
}
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<CheeseViewModel>()//创建viewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
val adapter = CheeseAdapter()//继承PagedListAdapter的类对象
cheeseList.adapter = adapter //为RecyclerView添加适配器
//viewmodel数据与adapter绑定,在数据变化时通知adapter更新UI
viewModel.allCheeses.observe(this, Observer(adapter::submitList))
initSwipeToDelete()//设置左滑/右滑删除数据项
initAddButtonListener()//设置点击添加Cheese功能
...
}
好了,大功告成!

你也可以尝试使用仅网络或网络+数据库的方式进行功能开发。
源码在此:
欢迎关注公众号,留言讨论更多技术问题

Jetpack系列:Paging组件帮你解决分页加载实现的痛苦的更多相关文章
- Jetpack 架构组件 Paging 分页加载 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- [转]微信小程序之加载更多(分页加载)实例 —— 微信小程序实战系列(2)
本文转自;http://blog.csdn.net/michael_ouyang/article/details/56846185 loadmore 加载更多(分页加载) 当用户打开一个页面时,假设后 ...
- java攻城狮之路(Android篇)--widget_webview_metadata_popupwindow_tabhost_分页加载数据_菜单
一.widget:桌面小控件1 写一个类extends AppWidgetProvider 2 在清单文件件中注册: <receiver android:name=".ExampleA ...
- Winform开发框架之客户关系管理系统(CRM)的开发总结系列4-Tab控件页面的动态加载
在前面介绍的几篇关于CRM系统的开发随笔中,里面都整合了多个页面的功能,包括多文档界面,以及客户相关信息的页面展示,这个模块就是利用DevExpress控件的XtraTabPage控件的动态加载实现的 ...
- android中滑动SQLite数据库分页加载
今天用到了android中滑动SQlit数据库分页加载技术,写了个测试工程,将代码贴出来和大家交流一下: MainActivity package com.example.testscrollsqli ...
- Android基本控件之listView(三)<用ListView实现分页加载>
我们之前讨论了ListView的基本使用方法和ListView的优化 今天我们再来讨论一个关于ListView的一个新的东西~就是分页加载.那么什么是分页加载呢?简单点说,就是"下拉刷新&q ...
- Android ListView分页加载时图片显示问题
场景:Android ListView需要分页加载,每个item中会有图片,图片又是从网络下载的. 问题:在滑动加载下一页时,上一页的图片明明已经下载完成了,但是无法显示出来. Bug重现: 1,加载 ...
- Android中ListView分页加载数据
public class MainActivity extends Activity { private ListView listView=null; //listview的数据填充器 privat ...
- ListView上拉刷新和分页加载完整的Dome
很多人工作的过程中都会碰到ListView下拉刷新和分页加载,然后大多数公司都已经把框架写好了,大家直接用就可以了,有些人一直对这个事情处于迷茫状态,为了让大家对上拉刷新和分页加载有一个比较全面的认识 ...
随机推荐
- Mongodb操作1-linux安装数据库
1.下载mongodb 百度云盘连接 :链接:https://pan.baidu.com/s/1b-hTS0XHQKpatecFoumLxw 提取码:z9ax 并送上可视化工具:链接:https:/ ...
- 【selenium】- webdriver常见api
本文由小编根据慕课网视频亲自整理,转载请注明出处和作者. 1.常见API 2.打开网址 3.操作浏览器 quit()没有完全关闭进程,依旧占用资源. 4.输入框操作 5.选择框操作 6.特殊窗口操作 ...
- Badboy参数化 - Add Variable(循环使用不同的关键字进行搜索)
参考: http://leafwf.blog.51cto.com/872759/1113716 http://www.51testing.com/html/00/130600-1367743.html ...
- P3084 [USACO13OPEN]照片Photo dp
题意: 有n个区间,每个区间只能有一个斑点奶牛,问最多有几个斑点奶牛. 思路: 首先要处理出每个点的L[i],R[i]. L[i]表示L[i]-i-1之间一定有一个点.i也是选中的. R[i]表示R[ ...
- POJ-3660 Cow Contest( 最短路 )
题目链接:http://poj.org/problem?id=3660 Description N (1 ≤ N ≤ 100) cows, conveniently numbered 1..N, ar ...
- The Sultan's Successors UVA - 167
the squares thus selected sum to a number at least as high as one already chosen by the Sultan. (For ...
- centos 6.5 系统故障分析实验
系统故障分析实验 日志文件分析 日志的功能 用于记录系统.程序运行中发生的各种事件 通过阅读日志,有助于诊断和解决系统故障 日志文件的分类 内核及系统日志 由系统服务syslog统一进行管理,日志格式 ...
- 【Spring】事务
一.数据库事务概述 二.Spring中事务 1. Spring 事务管理: 2. Spring 事务管理的API: 2.1 API概述 2.2 PlatformTransactionManager 接 ...
- Python的6种运算符(日记)
学习了许久的Python,我单独总结出了Python中比较常见的6种运算符,感觉略有不全,希望大伙可以一起讨论与研究Python! 一.算术运算符 加 减 - 乘 * 除 / 取余 % 取整 // 异 ...
- SQLServer的网络协议
一.总结 1.SQL Server访问协议包括Shared Memory.Named Pipes.TCP/IP.VIA四种,多数应用系统都是通过TCP/IP协议访问数据库.安装数据库后需要启用TCP/ ...