相信很多小伙伴们在项目实战中,经常会用到界面的分页显示加载更多等功能。需要针对具体功能做针对性开发和调试,耗时耗力。

Paging组件的使用将这部分的工作简化,从而让开发者更专注于业务的具体实现。下面我们一起来学习下Paging组件的使用方法。


首先来看下使用Paging组件实现的分页加载和刷新效果:

![](https://img2018.cnblogs.com/blog/1820853/201910/1820853-20191010103258092-718449477.gif)

数据库读取分页加载




![](https://img2018.cnblogs.com/blog/1820853/201910/1820853-20191010103309867-715023857.gif)


网络端分页请求数据

下面我们针对这两个使用Paging组件的例子进行分析。

  • 数据库读取分页加载示例中,数据一次性获取完成,界面分页显示,按需加载数据,减少了内存资源的使用
  • 网络端分页请求数据,每次请求固定长度的数据信息进行显示,减少网络带宽的使用

Paging功能的实现用到了Room组件,Room也是Jetpack库的一部分,在SQLite上提供了一个抽象层,为开发者提供了流畅的SQLite数据库访问体验。

Room简介

Room组件包含三个主要组成部分:

  • 数据库

其应该满足四个条件:

  1. 含有@Database注解
  2. 是一个继承自RoomDatabase的抽象类
  3. 注解内包含实体的列表信息
  4. 包含一个返回带@Dao注解类的无参方法
  • 数据实体

表示数据库中表

  • DAO

包含用于访问数据库的方法

应用程序使用Room组件获取与数据库关联的数据访问对象或DAO,然后获取实体,将实体的所有更改同步到数据库。Room三个部分之间的关系如下图:

![Room架构图](https://img2018.cnblogs.com/blog/1820853/201910/1820853-20191010104502674-737554548.png)


Room架构图(引自官方文档)

Paging的基本使用方法

Paging组件支持三种不同数据结构:

  • 仅从网络获取
  • 仅从设备数据库获取
  • 两种数据来源的组合,使用设备数据库作为缓存

![Paging支持数据架构](https://img2018.cnblogs.com/blog/1820853/201910/1820853-20191010104520872-165043899.png)


分页库支持数据架构(引自官方文档)

下面我们以仅从设备数据库获取的方式来了解下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进行数据获取和显示前,需要做几点准备工作:

  1. 创建数据实体类Cheese
  2. 创建数据库方法DAO
  3. 创建数据库CheeseDb
  4. 创建自定义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功能
...
}

好了,大功告成!

你也可以尝试使用仅网络或网络+数据库的方式进行功能开发。

源码在此:

数据库分页

网络请求分页

欢迎关注公众号,留言讨论更多技术问题

![file](https://img2018.cnblogs.com/blog/1820853/201910/1820853-20191010103323866-283198181.jpg)

Jetpack系列:Paging组件帮你解决分页加载实现的痛苦的更多相关文章

  1. Jetpack 架构组件 Paging 分页加载 MD

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

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

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

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

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

  4. Winform开发框架之客户关系管理系统(CRM)的开发总结系列4-Tab控件页面的动态加载

    在前面介绍的几篇关于CRM系统的开发随笔中,里面都整合了多个页面的功能,包括多文档界面,以及客户相关信息的页面展示,这个模块就是利用DevExpress控件的XtraTabPage控件的动态加载实现的 ...

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

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

  6. Android基本控件之listView(三)<用ListView实现分页加载>

    我们之前讨论了ListView的基本使用方法和ListView的优化 今天我们再来讨论一个关于ListView的一个新的东西~就是分页加载.那么什么是分页加载呢?简单点说,就是"下拉刷新&q ...

  7. Android ListView分页加载时图片显示问题

    场景:Android ListView需要分页加载,每个item中会有图片,图片又是从网络下载的. 问题:在滑动加载下一页时,上一页的图片明明已经下载完成了,但是无法显示出来. Bug重现: 1,加载 ...

  8. Android中ListView分页加载数据

    public class MainActivity extends Activity { private ListView listView=null; //listview的数据填充器 privat ...

  9. ListView上拉刷新和分页加载完整的Dome

    很多人工作的过程中都会碰到ListView下拉刷新和分页加载,然后大多数公司都已经把框架写好了,大家直接用就可以了,有些人一直对这个事情处于迷茫状态,为了让大家对上拉刷新和分页加载有一个比较全面的认识 ...

随机推荐

  1. Mongodb操作1-linux安装数据库

    1.下载mongodb 百度云盘连接 :链接:https://pan.baidu.com/s/1b-hTS0XHQKpatecFoumLxw  提取码:z9ax 并送上可视化工具:链接:https:/ ...

  2. 【selenium】- webdriver常见api

    本文由小编根据慕课网视频亲自整理,转载请注明出处和作者. 1.常见API 2.打开网址 3.操作浏览器 quit()没有完全关闭进程,依旧占用资源. 4.输入框操作 5.选择框操作 6.特殊窗口操作 ...

  3. Badboy参数化 - Add Variable(循环使用不同的关键字进行搜索)

    参考: http://leafwf.blog.51cto.com/872759/1113716 http://www.51testing.com/html/00/130600-1367743.html ...

  4. P3084 [USACO13OPEN]照片Photo dp

    题意: 有n个区间,每个区间只能有一个斑点奶牛,问最多有几个斑点奶牛. 思路: 首先要处理出每个点的L[i],R[i]. L[i]表示L[i]-i-1之间一定有一个点.i也是选中的. R[i]表示R[ ...

  5. POJ-3660 Cow Contest( 最短路 )

    题目链接:http://poj.org/problem?id=3660 Description N (1 ≤ N ≤ 100) cows, conveniently numbered 1..N, ar ...

  6. 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 ...

  7. centos 6.5 系统故障分析实验

    系统故障分析实验 日志文件分析 日志的功能 用于记录系统.程序运行中发生的各种事件 通过阅读日志,有助于诊断和解决系统故障 日志文件的分类 内核及系统日志 由系统服务syslog统一进行管理,日志格式 ...

  8. 【Spring】事务

    一.数据库事务概述 二.Spring中事务 1. Spring 事务管理: 2. Spring 事务管理的API: 2.1 API概述 2.2 PlatformTransactionManager 接 ...

  9. Python的6种运算符(日记)

    学习了许久的Python,我单独总结出了Python中比较常见的6种运算符,感觉略有不全,希望大伙可以一起讨论与研究Python! 一.算术运算符 加 减 - 乘 * 除 / 取余 % 取整 // 异 ...

  10. SQLServer的网络协议

    一.总结 1.SQL Server访问协议包括Shared Memory.Named Pipes.TCP/IP.VIA四种,多数应用系统都是通过TCP/IP协议访问数据库.安装数据库后需要启用TCP/ ...