目录

1、从本地Room数据库加载数据

viewmodel

fragment中使用

页面

数据库相关

2、直接网络获取数据加载

3、网络访问数据到Room数据库再加载数据

自定义RemoteMediator访问网络数据

4、封装使用

BaseViewHolder

列表单个item封装的基类

封装统一的adapter

使用方法

自定义PagingItemView

viewmodel获取数据后,把获取到的pagingData装到自定义的PagingItemView中

fragment中使用


1、从本地Room数据库加载数据

参照官方示例代码,在原代码基础上做了一个小改动增加了一个BaseViewHolder,这样不用每次都要新建一个viewholder,直接在adapter中编写绑定数据的代码

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerViewclass BaseViewHolder(viewGroup: ViewGroup, layoutRes: Int): RecyclerView.ViewHolder(LayoutInflater.from(viewGroup.context).inflate(layoutRes, viewGroup, false)
){fun bind( bindView: (View)-> Unit){bindView(itemView)}
}

绑定数据的代码移到adapter中,其他代码没有多少改变

class CheeseAdapter : PagingDataAdapter<CheeseListItem, BaseViewHolder>(diffCallback) {override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {holder.bind {val nameView = it.findViewById<TextView>(R.id.name)val item = getItem(position)if (item is CheeseListItem.Separator) {nameView.text = "${item.name} Cheeses"nameView.setTypeface(null, Typeface.BOLD)} else {nameView.text = item?.namenameView.setTypeface(null, Typeface.NORMAL)}nameView.text = item?.name}}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {return BaseViewHolder(parent,viewType)}override fun getItemViewType(position: Int): Int {return R.layout.home_item}companion object {/*** This diff callback informs the PagedListAdapter how to compute list differences when new* PagedLists arrive.** When you add a Cheese with the 'Add' button, the PagedListAdapter uses diffCallback to* detect there's only a single item difference from before, so it only needs to animate and* rebind a single view.** @see DiffUtil*/val diffCallback = object : DiffUtil.ItemCallback<CheeseListItem>() {override fun areItemsTheSame(oldItem: CheeseListItem, newItem: CheeseListItem): Boolean {return if (oldItem is CheeseListItem.Item && newItem is CheeseListItem.Item) {oldItem.cheese.id == newItem.cheese.id} else if (oldItem is CheeseListItem.Separator && newItem is CheeseListItem.Separator) {oldItem.name == newItem.name} else {oldItem == newItem}}/*** Note that in kotlin, == checking on data classes compares all contents, but in Java,* typically you'll implement Object#equals, and use it to compare object contents.*/override fun areContentsTheSame(oldItem: CheeseListItem, newItem: CheeseListItem): Boolean {return oldItem == newItem}}}
}
sealed class CheeseListItem(val name: String) {data class Item(val cheese: Cheese) : CheeseListItem(cheese.name)data class Separator(private val letter: Char) : CheeseListItem(letter.toUpperCase().toString())
}

viewmodel

class AccountBookViewModel(private val dao: CheeseDao) : BaseViewModel() {/*** We use the Kotlin [Flow] property available on [Pager]. Java developers should use the* RxJava or LiveData extension properties available in `PagingRx` and `PagingLiveData`.*/val allCheeses: Flow<PagingData<CheeseListItem>> = Pager(config = PagingConfig(pageSize = 10,enablePlaceholders = true,/*** Maximum number of items a PagedList should hold in memory at once.** This number triggers the PagedList to start dropping distant pages as more are loaded.*/maxSize = 200)) {dao.allCheesesByName()}.flow.map { pagingData ->pagingData// Map cheeses to common UI model..map { cheese -> CheeseListItem.Item(cheese) }.insertSeparators { before: CheeseListItem?, after: CheeseListItem? ->if (before == null && after == null) {// List is empty after fully loaded; return null to skip adding separator.null} else if (after == null) {// Footer; return null here to skip adding a footer.null} else if (before == null) {// HeaderCheeseListItem.Separator(after.name.first())} else if (!before.name.first().equals(after.name.first(), ignoreCase = true)){// Between two items that start with different letters.CheeseListItem.Separator(after.name.first())} else {// Between two items that start with the same letter.null}}}.cachedIn(viewModelScope)}

fragment中使用

class AccountBookFragment : BaseViewBindingFragment<FragmentAccountBookBinding>() {override fun getViewBinding() = FragmentAccountBookBinding.inflate(layoutInflater)private val mViewModel: AccountBookViewModel by viewModels {object : ViewModelProvider.Factory{override fun <T : ViewModel> create(modelClass: Class<T>): T {val cheeseDao = LocalDb.get(BaseApplication.instance).cheeseDao()return AccountBookViewModel(cheeseDao)as T}}}override fun initView() {val adapter = CheeseAdapter()mViewBinding?.cheeseList?.adapter = adapterlifecycleScope.launch {mViewModel.allCheeses.collectLatest { adapter.submitData(it) }}}}

页面

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".ui.accountbook.AccountBookFragment"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/cheeseList"app:layout_behavior="@string/appbar_scrolling_view_behavior"android:layout_width="match_parent"android:layout_height="match_parent"android:scrollbars="vertical"app:layoutManager="LinearLayoutManager"/>
</androidx.constraintlayout.widget.ConstraintLayout>

数据库相关

/*** Data class that represents our items.*/
@Entity
data class Cheese(@PrimaryKey(autoGenerate = true) val id: Int, val name: String)
/*** Database Access Object for the Cheese database.*/
@Dao
interface CheeseDao {/*** Room knows how to return a LivePagedListProvider, from which we can get a LiveData and serve* it back to UI via ViewModel.*/@Query("SELECT * FROM Cheese ORDER BY name COLLATE NOCASE ASC")fun allCheesesByName(): PagingSource<Int, Cheese>@Insertfun insert(cheeses: List<Cheese>)@Insertfun insert(cheese: Cheese)@Deletefun delete(cheese: Cheese)
}
/*** Singleton database object. Note that for a real app, you should probably use a Dependency* Injection framework or Service Locator to create the singleton database.*/
@Database(entities = [Cheese::class,UserBean::class,WaitDealBean::class], version = 1)
abstract class LocalDb : RoomDatabase() {abstract fun cheeseDao(): CheeseDaoabstract fun userDao(): UserDaoabstract fun waitdealDao(): WaitDealDaocompanion object {private var instance: LocalDb? = null@Synchronizedfun get(context: Context): LocalDb {if (instance == null) {instance = Room.databaseBuilder(context.applicationContext,LocalDb::class.java, "TestDatabase").addCallback(object : RoomDatabase.Callback() {override fun onCreate(db: SupportSQLiteDatabase) {fillInDb(context.applicationContext)}}).build()}return instance!!}/*** fill database with list of cheeses*/private fun fillInDb(context: Context) {// inserts in Room are executed on the current thread, so we insert in the backgroundioThread {get(context).cheeseDao().insert(CHEESE_DATA.map { Cheese(id = 0, name = it) })}}}
}private val CHEESE_DATA = arrayListOf("Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi","Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale","Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese","Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell","Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc","Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss","Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon","Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase","Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese","Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa","Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois","Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue","Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington","Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou","Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue","Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano")

文件名和类名不要太在意,因为参考的官网示例,做具体业务的时候再自己调整

2、直接网络获取数据加载

参考1、从本地Room数据库加载数据

自定义pagingsource访问网络数据

class WaitDealDataSource : PagingSource<Int, WaitDealBean>() {override fun getRefreshKey(state: PagingState<Int, WaitDealBean>): Int? = nulloverride suspend fun load(params: LoadParams<Int>): LoadResult<Int, WaitDealBean> {val nextPageNumber = params.key ?: 1val size = params.loadSizeval result = NetworkService.api.getPlanList(nextPageNumber, size,"planTime")return LoadResult.Page(data = if(result.success) result.data!! else ArrayList(),prevKey = null, // Only paging forward.  只向后加载就给 null//nextKey 下一页页码;  尾页给 null;  否则当前页码加1nextKey = if(result.currentPage!! >= result.totalPageSize!!) null else (nextPageNumber + 1))}
}

ViewModel中使用

 val allWaitDeal: Flow<PagingData<WaitDealItem>> = Pager(config = PagingConfig(pageSize = 10,enablePlaceholders = true)) {WaitDealDataSource()}.flow.map { pagingData ->pagingData// Map cheeses to common UI model..map { cheese -> WaitDealItem.Item(cheese) }.insertSeparators { before: WaitDealItem?, after: WaitDealItem? ->if (before == null && after == null) {// List is empty after fully loaded; return null to skip adding separator.null} else if (after == null) {// Footer; return null here to skip adding a footer.null} else if (before == null) {// HeaderWaitDealItem.Separator(after.sTime.substring(0,10))} else if (!before.sTime.substring(0,10).equals(after.sTime.substring(0,10), ignoreCase = true)){// Between two items that start with different letters.WaitDealItem.Separator(after.sTime.substring(0,10))} else {// Between two items that start with the same letter.null}}}.cachedIn(viewModelScope)

3、网络访问数据到Room数据库再加载数据

参考1、从本地Room数据库加载数据

自定义RemoteMediator访问网络数据

@OptIn(ExperimentalPagingApi::class)
class WaitDealRemoteMediator (private val pageSize: Int,private val database: LocalDb
) : RemoteMediator<Int, WaitDealBean>() {val waitDealDao = database.waitdealDao()override suspend fun initialize(): InitializeAction {return InitializeAction.SKIP_INITIAL_REFRESH}override suspend fun load(loadType: LoadType,state: PagingState<Int, WaitDealBean>): MediatorResult {return try {val pageNum = when (loadType) {//首次访问 或者调用 PagingDataAdapter.refresh()时LoadType.REFRESH -> 1//在当前加载的数据集的开头加载数据时LoadType.PREPEND ->return MediatorResult.Success(true)//下拉加载更多时LoadType.APPEND -> {val lastItem = state.lastItemOrNull()if (lastItem == null) {//最后一项为空直接返回return MediatorResult.Success( true)}//获取最后一个pageval page = state.pages[state.pages.size - 1]//下一页pageNum = ((列表总数量)/每页数量 )+1(page.itemsBefore+page.data.size)/ pageSize+1}}//无网络则加载本地数据if (!NetworkUtils.isConnected()) {return MediatorResult.Success(endOfPaginationReached = true)}//region 更新数据库val response = NetworkService.api.getPlanList(pageNum,pageSize)database.withTransaction {if (loadType == LoadType.REFRESH) {waitDealDao.clearAll()}waitDealDao.insertAll(response.data)}//endregion//返回true则表示加载完成,返回false会继续加载下一页数据val isFinished = response.data == null || response.data!!.size.toLong() >= response.totalSize!!MediatorResult.Success(isFinished)} catch (e: Exception) {ExceptionUtil.catchException(e)MediatorResult.Error(e)}}
}

使用其他地方不变,在pager中添加remoteMediator参数即可

4、封装使用

BaseViewHolder

class BaseViewHolder(val viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder(LayoutInflater.from(viewGroup.context).inflate(viewType, viewGroup, false)
) 

列表单个item封装的基类

import androidx.annotation.LayoutRes/*** 单个item*/
abstract class PagingItemView<T : Any>(@LayoutRes val layoutRes: Int,val entity: T) {abstract fun onBindView(holder: BaseViewHolder, position: Int)abstract fun areItemsTheSame(data: T): Booleanabstract fun areContentsTheSame(data: T): Boolean
}

封装统一的adapter


import android.content.Context
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView/*** 统一的adapter*/
class PagingAdapter<T:Any>(val context: Context) :  PagingDataAdapter<PagingItemView<T>, RecyclerView.ViewHolder>(object : DiffUtil.ItemCallback<PagingItemView<T>>() {override fun areItemsTheSame(oldItem: PagingItemView<T>, newItem: PagingItemView<T>): Boolean {return oldItem.areItemsTheSame(newItem.entity)}override fun areContentsTheSame(oldItem: PagingItemView<T>, newItem: PagingItemView<T>): Boolean {return oldItem.areContentsTheSame(newItem.entity)}}
){// 获取到对应的itemview去调用onBindView方法设置UIoverride fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {if (position != RecyclerView.NO_POSITION) {getItem(position)?.onBindView(holder = holder as BaseViewHolder, position = position)}}// 这里用itemView的layoutRes去作为viewtype,这样不同布局的itemview就可以区分开来override fun getItemViewType(position: Int): Int {return getItem(position)!!.layoutRes}// 因为上面是用layoutRes来作为itemType,所以创建viewholder的时候直接用viewType来创建override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {return BaseViewHolder(parent,viewType)}}

使用方法

自定义PagingItemView

class ItemWaitDeal(entity: WaitDealBean) : PagingItemView<WaitDealBean>(R.layout.wait_deal_item,entity) {override fun areItemsTheSame(data: WaitDealBean): Boolean {return entity.id!! == data.id}override fun areContentsTheSame(data: WaitDealBean): Boolean {return entity.id!! == data.id}override fun onBindView(holder: BaseViewHolder, position: Int) {val tvDay  = holder.itemView.findViewById<TextView>(R.id.tv_day)val tvMinute  = holder.itemView.findViewById<TextView>(R.id.tv_minute)val titleView  = holder.itemView.findViewById<TextView>(R.id.title)val contentView  = holder.itemView.findViewById<TextView>(R.id.content)titleView.text = entity.titlecontentView.text = entity.contentcontentView.visibility =  if(StringUtils.isTrimEmpty(entity.content)) View.GONE else View.VISIBLEval planTime = entity.planTime?.substring(0,10)tvDay.text = planTime}
}

viewmodel获取数据后,把获取到的pagingData装到自定义的PagingItemView中

class WaitDealViewModel : BaseViewModel() {fun <T : Any> queryData(): Flow<PagingData<PagingItemView<T>>> {val database = LocalDb.get(BaseApplication.instance)val NETWORK_PAGE_SIZE = 10@OptIn(ExperimentalPagingApi::class)return Pager(config = PagingConfig(pageSize = NETWORK_PAGE_SIZE,enablePlaceholders = false),//访问网络数据,检查数据更新remoteMediator = WaitDealRemoteMediator(NETWORK_PAGE_SIZE, database)) {//查询数据库数据database.waitdealDao().queryAll()}.flow.map { pagingData->pagingData.map { item ->//转换数据ItemWaitDeal(item) as (PagingItemView<T>)}}.cachedIn(viewModelScope)}}

fragment中使用

class WaitDealFragment : BaseViewBindingFragment<FragmentWaitDealBinding>() {override fun getViewBinding() = FragmentWaitDealBinding.inflate(layoutInflater)private val mViewModel: WaitDealViewModel by viewModels{object : ViewModelProvider.Factory{override fun <T : ViewModel> create(modelClass: Class<T>): T {return WaitDealViewModel()  as T}}}override fun initView() {val pagingAdapter = PagingAdapter<WaitDealBean>(requireContext())mViewBinding?.recyclerView?.layoutManager = LinearLayoutManager(context)mViewBinding?.recyclerView?.adapter = pagingAdaptermViewBinding?.swipeRefreshLayout?.bindAdapter(pagingAdapter)lifecycleScope.launch {mViewModel.queryData<WaitDealBean>().collectLatest { pagingAdapter.submitData(it) }}}}

Paging3、Room使用,1、从本地Room数据库加载 2、直接网络获取数据加载 3、网络访问数据到Room数据库再加载 4、封装使用相关推荐

  1. c 访问阿里云mysql_本地怎样访问阿里云mysql数据库服务器

    全网最新活动请看下方内容或右侧内容! --------------- 本地怎样访问阿里云mysql数据库服务器,在阿里云上放数据库. 对于大多数小型或初期项目来说,我们可能常用的做法是先将web.数据 ...

  2. sparksql删除MySQL数据_Databricks 第6篇:Spark SQL 维护数据库和表

    Spark SQL 表的命名方式是db_name.table_name,只有数据库名称和数据表名称.如果没有指定db_name而直接引用table_name,实际上是引用default 数据库下的表. ...

  3. 通过PL/SQL developer工具访问远程的Oracle数据库_访问数据库_连接数据库_登录数据库

    文章目录 工具简介 电脑没有安装 Oracle 数据库 电脑安装了 Oracle 数据库 工具简介 PL/SQL Developer 是 Oracle 数据库开发工具,PL/SQL Developer ...

  4. sql数据库查询聚合函数_如何使用SQL Server数据质量服务确保正确的数据聚合

    sql数据库查询聚合函数 介绍 (Introduction) An interesting opportunity arose at a client site during early Octobe ...

  5. redis 公网ip访问_怎样从公网访问内网Redis数据库

    公网访问内网Redis数据库 本地安装了Redis数据库,只能在局域网内访问,怎样从公网也能访问本地Redis数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Redis数据库 ...

  6. 河北省地税数据上收集中及异地容灾应用(RealSync数据库同步复制)

    河北地税征管业务选用DSG RealSync用于省中心集中容灾和数据上收功能 (2005年8月)    河北省地税税收业务系统目前采用了在各市局分散应用模式,即十一个市局分别有各自的数据中心,负责税务 ...

  7. 如何把采集到的数据存入mysql_数据采集教程_数据发布_如何发布到数据库MySQL_后羿采集器...

    如果大家在发布到数据库时遇到一些问题,请参考这个教程进行问题排查:发布到数据库常见问题 作为一款真免费的数据采集软件,我们免费提供多种导出方式,如果小伙伴们需要将采集到的数据发布到数据库MySQL,可 ...

  8. 通过ip地址访问操作远程Mysql数据库

    通过ip地址访问操作远程Mysql数据库(数据库学习笔记 (三)) 可能使用的工具· 问题描述 远程数据库配置 本地连接 可能使用的工具· Navicat Premium XXX版本 各类数据库(仅以 ...

  9. 数据仓库Hive编程——HiveQL的数据定义(一):Hive中的数据库

    分类目录:商业智能<数据仓库Hive编程>总目录 相关文章: HiveQL的数据定义(一):Hive中的数据库 HiveQL的数据定义(二):修改数据库 HiveQL的数据定义(三):创建 ...

最新文章

  1. 英语词汇(5)followed by / sung by / written by
  2. 解决 Oralce 执行set autotrace on时的SP2-0618和SP2-0611错误
  3. 使用docker快速搭建nginx+php环境
  4. android UI进阶之实现listview的分页加载
  5. C#的变迁史04 - C# 4.0 之多线程篇
  6. android jxl.jar 使用,使用jxl.jar在Android中操作Excel表格——重中之重——对隐藏表的处理...
  7. python私有化方法_Python 私有化
  8. curl有时获取不到数据 什么原因导致_缓存击穿导致 golang 组件死锁的问题分享...
  9. .Net中常用的几种ActionResult
  10. 显卡煲机测试软件,铁三角耳机煲机方法三分钟让您学会煲耳机
  11. android日历的使用技巧,android日历控件的使用
  12. 关于conime.exe
  13. CGAL几何库配置教程
  14. javascript typeof 和 instanceof 的区别和联系
  15. https://github.com/qiangqiang666/demo
  16. android蓝牙健康 iee,Wi-Fi RTT(IEEE 802.11mc)
  17. 开机动画适配方案_修改开机动画教程
  18. win10装win7装win7
  19. 如何发现网站被劫持被黑DNS被污染
  20. 郭逸淵:4月7日比特幣(BTC)以太坊(ETH)行情分析及布局思路

热门文章

  1. 物联网定位技术,你了解多少?
  2. Flask Cookies
  3. 你不努力,有什么资格抱怨(转)(文/蒋文伟)
  4. PHP入门-面向对象
  5. 测试用例方法之等价类
  6. 预告 | 旷视成都研究院负责人刘帅成:图像对齐技术及其应用
  7. 如何设计可以动态扩容缩容的分库分表方案?
  8. 利用时间修改工具进行二次开发
  9. 用WebStorm搭建vue项目
  10. android switch低版本,任天堂 Switch 已支持刷入基于 Android 10 的 LineageOS 17.1 系统