如果要看3种完整的DataSource代码请到这里 https://www.jianshu.com/p/fd00c0fbd774
参考文章
https://juejin.im/entry/5a6fd4b7f265da3e261c3cc4
https://developer.android.com/reference/android/arch/paging/DataSource
http://www.loongwind.com/archives/367.html
之前用的PagedList.Builder生成的PagedList,然后用loadAround刷新,不太好使啊,改天研究为啥。
现在改用LivePagedListBuilder生成LiveData然后添加监听
data.observe(this, Observer {
println("98==================observer====${it?.size}")
getAdapter().submitList(it)
})
room还没学
https://blog.csdn.net/youth_never_go_away/article/details/79902879
Datasource
顾名思义, Datasource<Key,Value>是数据源相关的类,其中 Key对应加载数据的条件信息, Value对应返回结果, 针对不同场景,Paging 提供了三种 Datasource:
PageKeyedDataSource <Key , Value> :适用于目标数据根据页信息请求数据的场景,即 Key 字段是页相关的信息。比如请求的数据的参数中包含类似 next /previous的信息。
ItemKeyedDataSource <Key , Value> :适用于目标数据的加载依赖特定item的信息, 即Key字段包含的是Item中的信息,比如需要根据第N项的信息加载第N+1项的数据,传参中需要传入第N项的ID时,该场景多出现于论坛类应用评论信息的请求。
PositionalDataSource <T > :适用于目标数据总数固定,通过特定的位置加载数据,这里 Key是Integer类型的位置信息, T即 Value。 比如从数据库中的1200条开始加在20条数据。
以上三种 Datasource 都是抽象类, 使用时需实现请加载数据的方法。三种Datasource 都需要实现 loadInitial()方法, 各自都封装了请求初始化数据的参数类型 LoadInitialParams。 不同的是分页加载数据的方法, PageKeyedDataSource和 ItemKeyedDataSource比较相似, 需要实现 loadBefore()和 loadAfter () 方法,同样对请求参数做了封装,即 LoadParams<Key>。 PositionalDataSource需要实现 loadRange () ,参数的封装类为 LoadRangeParams。
如果项目中使用 Android 架构组件中的 Room, Room 可以创建一个产出 PositionalDataSource的 DataSource .Factory:
先贴下代码
数据有问题,从0到29完事就都成了16到29拉,刚学习写还不太懂逻辑
现在对代码修改过,可以正常加载了
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_paging)
defaultSetTitle("page")
//recyclerview 设置adapter以及分割线
rv_paging.apply {
layoutManager = LinearLayoutManager(this@ActivityPaging)
//弄条分割线
addItemDecoration(object : RecyclerView.ItemDecoration() {
var paint = Paint()
override fun getItemOffsets(outRect: Rect, view: View?, parent: RecyclerView?, state: RecyclerView.State?) {
super.getItemOffsets(outRect, view, parent, state)
outRect.bottom = 3
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State?) {
super.onDraw(c, parent, state)
paint.color = Color.LTGRAY
var childCount = parent.childCount
repeat(childCount) {
var child = parent.getChildAt(it)
if (child != null) {
c.drawRect(parent.paddingLeft.toFloat(), child.bottom.toFloat(), parent.width.toFloat() - parent.paddingRight, child.bottom + 3f, paint)
}
}
}
})
adapter = MyPagingAdapter(callback)
}
//创建pageList
makePageList()
}
private fun getAdapter(): MyPagingAdapter {
return rv_paging.adapter as MyPagingAdapter
}
private fun makePageList() {
val mPagedListConfig = PagedList.Config.Builder()
.setPageSize(10) //分页数据的数量。在后面的DataSource之loadRange中,count即为每次加载的这个设定值。
.setPrefetchDistance(10) //提前多少数据开始继续加载数据。如果是上滑的话对应的就是离最后个item还有2个位置的时候开始记载更多数据。下拉一样的道理
.setInitialLoadSizeHint(10)
.setEnablePlaceholders(false)
.build()
//不建议这种,因为还得自己处理Executor,建议使用下边注释的代码
var mPagedList = PagedList.Builder(MyDataSource(), mPagedListConfig)
.setNotifyExecutor {
println("setNotifyExecutor=============1=====${Thread.currentThread().name}")//进来是非主线程pool-7-thread-1,因为这个是更新ui的,所以现编切换到主线程
Handler(Looper.getMainLooper()).post(it)//弄个全局变量,不要每次都new一个,我这里就方便测试
}
.setFetchExecutor {
println("setFetchExecutor=========1=========${Thread.currentThread().name}") //这里进来的是main线程,因为你要加载数据,所以切换线程
Executors.newFixedThreadPool(2).execute(it) //这里不应该每次都new一个,因改写个全局变量
}
.setInitialKey(initKey)//不设置的话默认就是0
.build()
getAdapter().submitList(mPagedList)
//如果懒得自己处理setNotifyExecutor和setFetchExecutor,建议用下边的,系统都有默认值,省事
// LivePagedListBuilder(object : DataSource.Factory<Int, Student>() {
// override fun create(): DataSource<Int, Student> {
// return MyDataSource() //DataSource有3种,这里就简单随便写了个,自己看需求写
// }
// }, mPagedListConfig)
// .build()
// .observe(this, Observer {
// getAdapter().submitList(it)
// })
}
var initKey=20;//初始从哪个位置开始加载数据,这里测试从第20条开始,这样下拉可以看到前20条数据,上拉可以看到20之后的数据
inner class MyDataSource : PositionalDataSource<Student>() {
private fun loadRangeInternal(startPosition: Int, loadCount: Int): List<Student>? {
// actual load code here
if (startPosition > 70) { //模拟数据加载完的情况
return null
}
var list = arrayListOf<Student>()
repeat(loadCount) {
list.add(Student(startPosition + it + 1, "stu ${startPosition + it + 1}"))
}
return list
}
override fun loadInitial(@NonNull params: PositionalDataSource.LoadInitialParams,
@NonNull callback: PositionalDataSource.LoadInitialCallback<Student>) {
//加载数据这里可以自己根据实际需求来处理params.requestedStartPosition就是我们设置的initKey=20
loadRangeInternal(params.requestedStartPosition, params.requestedLoadSize)?.apply {
callback.onResult(this, params.requestedStartPosition)
}
}
override fun loadRange(@NonNull params: PositionalDataSource.LoadRangeParams,
@NonNull callback: PositionalDataSource.LoadRangeCallback<Student>) {
//加载数据这里可以自己根据实际需求来处理
println("132=====load range ${params.startPosition}==${params.loadSize}===${Thread.currentThread().name}")
loadRangeInternal(params.startPosition, params.loadSize)?.apply {
callback.onResult(this)
}
}
}
//adapter和我们以前的没太大区别,就是构造方法里需要传一个参数,用来判断是否是相同数据而已
open inner class MyPagingAdapter : PagedListAdapter<Student, BaseRvHolder> {
constructor(diffCallback: DiffUtil.ItemCallback<Student>) : super(diffCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseRvHolder {
return BaseRvHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_paging, parent, false))
}
override fun onBindViewHolder(holder: BaseRvHolder, position: Int) {
getItem(position)?.apply {
holder.setText(R.id.tv_name, name)
holder.setText(R.id.tv_age, "${age}")
}
println("onBindViewHolder=============$position//${itemCount} ===${getItem(position)}")
}
}
val callback = object : DiffUtil.ItemCallback<Student>() {
override fun areItemsTheSame(oldItem: Student?, newItem: Student?): Boolean {
return oldItem?.id == newItem?.id
}
override fun areContentsTheSame(oldItem: Student?, newItem: Student?): Boolean {
return oldItem?.age == newItem?.age
}
}
看看源码好分析
看日志知道,首先执行了一次DataSource里的loadInitial的方法。
所以查找下这个方法啥时候执行的
首先会蹦到PositionalDataSource 的代码里。【因为我上边用的就是这个DataSource,如果是其他2种datasource,就会跳到对应的类里了】
然后一层一层往上,最后会发现走到了ContiguousPagedList的构造方法里
看下2张图
image.png image.png
而这两个构造方法都是在PagedList创建的时候执行的,如下图
所以我们就知道,在PagedList初始化的时候,DataSource里的loadInitial方法就会执行一次。
image.png
看上图的if条件,我们知道datasource的isContiguous为true或者config的enablePlaceholers为false的时候是ContigousPageList,其他的是TiledPageList
我们知道datasource有3种,其中有2个都是继承一个父类的,pagekeydDatasource和ItemKeyedDatasrouce都是继承ContigousDatasource【看名字就知道这2个isContiguous为true了】,另外一个positionalDataSource就是false拉。
分析为啥pageSize我设置为10,结果loadInitial里默认加载就是30条,一就是3倍?
一路点击这个LoadInitialParams 哪里来的,最后发现它就是PagedList里Config类里配置的。
看下Config的Build类
public static final class Builder {
private int mPageSize = -1;
private int mPrefetchDistance = -1;
private int mInitialLoadSizeHint = -1;
private boolean mEnablePlaceholders = true;
public Config build() {
if (mPageSize < 1) {
throw new IllegalArgumentException("Page size must be a positive number");
}
if (mPrefetchDistance < 0) {
mPrefetchDistance = mPageSize;
}
if (mInitialLoadSizeHint < 0) {
mInitialLoadSizeHint = mPageSize * 3;//因为我们的config啥都没配置,就设置了一个pagesize
}
if (!mEnablePlaceholders && mPrefetchDistance == 0) {
//这里也要注意,mEnablePlaceholders =false的时候,预加载的个数不能为0
throw new IllegalArgumentException("Placeholders and prefetch are the only ways"
+ " to trigger loading of more data in the PagedList, so either"
+ " placeholders must be enabled, or prefetch distance must be > 0.");
}
return new Config(mPageSize, mPrefetchDistance,
mEnablePlaceholders, mInitialLoadSizeHint);
}
看下PagedList的Builder构造方法,就是我们demo里用的这个,其实也就是传了一个Config
,修改后的demo传了个config,最早用的就是下边的,就传了个pagesize
public Builder(@NonNull DataSource<Key, Value> dataSource, int pageSize) {
this(dataSource, new PagedList.Config.Builder().setPageSize(pageSize).build());
}
修改config的时候又挂了。
结论:setInitialLoadSizeHint的大小必须是pagesize的倍数,也就是必须可以整除
val mPagedListConfig = PagedList.Config.Builder()
.setPageSize(5) //分页数据的数量。在后面的DataSource之loadRange中,count即为每次加载的这个设定值。
.setPrefetchDistance(5) //初始化时候,预取数据数量。
.setInitialLoadSizeHint(6)//这玩意不能瞎写,这里写个6就挂了,下边分析原因
.setEnablePlaceholders(false)
.build()
override fun loadInitial(@NonNull params: PositionalDataSource.LoadInitialParams,
@NonNull callback: PositionalDataSource.LoadInitialCallback<Student>) {
val totalCount = computeCount()
val position = PositionalDataSource.computeInitialLoadPosition(params, totalCount)
val loadSize = PositionalDataSource.computeInitialLoadSize(params, position, totalCount)
//然后下边就挂了。我初始加载了6条数据
callback.onResult(loadRangeInternal(position, loadSize), position, totalCount)
}
走到了下边这里,然后分析下
image.png
可以看到,我们初始的时候data大小是6,完事pagesize是5,这个求余肯定不是0了。
上边datasource有3种,而这里pagedlist有2种,根据datasource来的
if (dataSource.isContiguous() || !config.enablePlaceholders){
ContiguousPagedList
}else{
return new TiledPagedList<>
}
继续看PageKeyedDataSource和ItemKeyedDataSource都是contiguous的
/**
* Incremental data loader for page-keyed content, where requests return keys for next/previous
* pages.
* <p>
* Implement a DataSource using PageKeyedDataSource if you need to use data from page {@code N - 1}
* to load page {@code N}. This is common, for example, in network APIs that include a next/previous
* link or key with each page load.
* <p>
* The {@code InMemoryByPageRepository} in the
* <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
* shows how to implement a network PageKeyedDataSource using
* <a href="https://square.github.io/retrofit/">Retrofit</a>, while
* handling swipe-to-refresh, network errors, and retry.
*
* @param <Key> Type of data used to query Value types out of the DataSource.
* @param <Value> Type of items being loaded by the DataSource.
*/
public abstract class PageKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value>
/**
* Incremental data loader for paging keyed content, where loaded content uses previously loaded
* items as input to future loads.
* <p>
* Implement a DataSource using ItemKeyedDataSource if you need to use data from item {@code N - 1}
* to load item {@code N}. This is common, for example, in sorted database queries where
* attributes of the item such just before the next query define how to execute it.
* <p>
* The {@code InMemoryByItemRepository} in the
* <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
* shows how to implement a network ItemKeyedDataSource using
* <a href="https://square.github.io/retrofit/">Retrofit</a>, while
* handling swipe-to-refresh, network errors, and retry.
*
* @param <Key> Type of data used to query Value types out of the DataSource.
* @param <Value> Type of items being loaded by the DataSource.
*/
public abstract class ItemKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value>
到底怎么才能自动加载数据
目前是使用如下的代码解决的,也就是不是直接build一个pagelist,而是用如下的代码生成一个包含pagelist的livedata,然后添加observer
LivePagedListBuilder(MyDataSourceFactory(), getPageConfig()).build().observe(this, Observer {
println("base 34==================observer====${it?.size}")
getAdapter().submitList(it)
})
这两天我就仔细的看了一遍又一遍LivePagedListBuilder构建的PageList和我直接build的到底有啥区别。真是没看出来啊。
首先看下原来有问题的,在实际中,我们可以看到setFetchExecutor 里的方法有执行过,看名字也像是获取数据用的,一直没当回事
PagedList.Builder(MyDataSource(), mPagedListConfig)
.setNotifyExecutor {
println("setNotifyExecutor=============1=====${Thread.currentThread().name}")
}
.setFetchExecutor {
println("setFetchExecutor=========1=========${Thread.currentThread().name}")
}
.build()
今天心血来潮,我就把那个正常的也加了这个,如下,然后我就发现它没数据了。
LivePagedListBuilder(object :DataSource.Factory<Int,Student>(){
override fun create(): DataSource<Int, Student> {
return MyDataSource()
}
}, mPagedListConfig)
.setFetchExecutor {
println("setFetchExecutor=========1=========${Thread.currentThread().name}")
}
.build()
setFetchExecutor 有个默认值,那就来看下默认值都干啥了,为啥用默认值可以加载出数据?
public final class LivePagedListBuilder<Key, Value> {
private Executor mFetchExecutor = ArchTaskExecutor.getIOThreadExecutor();
长这样,额,原来默认的里边是有执行这个runnable了,而我们写的就打印了下,根本没操作这个runnable。
private static final Executor sIOThreadExecutor = new Executor() {
@Override
public void execute(Runnable command) {
getInstance().executeOnDiskIO(command);
}
};
终于解决了为啥不能加载更多的数据,代码如下
setNotifyExecutor 和setFetchExecutor 是关键啊,这两个里边返回的runnable是需要我们来执行了。还以为这个就是看的。
var mPagedList = PagedList.Builder(MyDataSource(), mPagedListConfig)
.setNotifyExecutor {
println("setNotifyExecutor=============1=====${Thread.currentThread().name}")//进来是非主线程pool-7-thread-1,因为这个是更新ui的,所以下边切换到主线程
Handler(Looper.getMainLooper()).post(it)//弄个全局变量,不要每次都new一个,我这里就方便测试
}
.setFetchExecutor {
println("setFetchExecutor=========1=========${Thread.currentThread().name}") //这里进来的是main线程,因为你要加载数据,所以切换线程
Executors.newFixedThreadPool(2).execute(it) //这里不应该每次都new一个,因改写个全局变量
}
.build()
getAdapter().submitList(mPagedList)
简单分析下是如何实现自动加载数据的
这就要看新的adapter了
//PagedListAdapter里获取数据的方法如下
protected T getItem(int position) {
return mDiffer.getItem(position);
}
//继续进入mDiffer里查看
public T getItem(int index) {
if (mPagedList == null) {
if (mSnapshot == null) {
throw new IndexOutOfBoundsException(
"Item count is zero, getItem() call is invalid");
} else {
return mSnapshot.get(index);
}
}
//这玩意就是用来加载数据的,看名字就是加载自己附近的数据的
mPagedList.loadAround(index);
return mPagedList.get(index);
}
/**
* Load adjacent items to passed index.
*
* @param index Index at which to load.
*/
public void loadAround(int index) {
mLastLoad = index + getPositionOffset();
loadAroundInternal(index);
mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
/*
* mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
* dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
* and accesses happen near the boundaries.
*
* Note: we post here, since RecyclerView may want to add items in response, and this
* call occurs in PagedListAdapter bind.
*/
tryDispatchBoundaryCallbacks(true);
}
其中loadAroundInternal(index);
这个就是在根据当前加载是数据,判断是否可以加载前边的数据,以及后边的数据,就比如我们demo里,我初始是从position为20开始加载数据的,pagesize为10的话,这里就会预加载前10条数据,以后后10条数据,也就是10到19,以及30到39
至于tryDispatchBoundaryCallbacks这个,就是处理数据加载边界值一些回调,因为我们的pagelist也没设置callback,所以不研究了。
大家可以看下这个回调的方法都有啥,有需要可以自己加
public abstract static class BoundaryCallback<T> {
/**
* Called when zero items are returned from an initial load of the PagedList's data source.
*/
public void onZeroItemsLoaded() {}
/**
* Called when the item at the front of the PagedList has been loaded, and access has
* occurred within {@link Config#prefetchDistance} of it.
* <p>
* No more data will be prepended to the PagedList before this item.
*
* @param itemAtFront The first item of PagedList
*/
public void onItemAtFrontLoaded(@NonNull T itemAtFront) {}
/**
* Called when the item at the end of the PagedList has been loaded, and access has
* occurred within {@link Config#prefetchDistance} of it.
* <p>
* No more data will be appended to the PagedList after this item.
*
* @param itemAtEnd The first item of PagedList
*/
public void onItemAtEndLoaded(@NonNull T itemAtEnd) {}
}
测试这个方法的时候又发现以前写法有问题,所以可能导致上边的回调不会执行
-
1 onZeroItemsLoaded
这个方法啥时候会回调?就是我们DataSource里的loadInitial方法里回调的result的size为0的时候,也就是第一次加载就没数据会走这里。
需要注意的地方,对于PositionalDataSource 这种,如果你initKey,也就是首次加载的position设置的不是0,而你又返回一个空的数据,那么会挂掉的。loadinitial允许返回size为0的数据,但你必须从0开始 -
2 onItemAtFrontLoaded
这个就是如果加载到了最顶部的数据的时候会回调,对于PositionalDataSource 而言,就是加载了position为0的数据的时候就会走这里了,对于ItemKeyedDataSource就是loadBefore方法里 callback.onResult(this)的this的size为0的时候,PageKeyedDataSource也一样的道理 -
3 onItemAtEndLoaded
加载到没有数据的时候会返回最后一条数据,也就是在loadRange里的callback.onResult(this),这个this集合的大小为0的时候,所以啊如果要监听到onItemAtEndLoaded这个回调,你的callback.onResult必须调用,返回一个size为0的集合。而不是我们上边demo的代码,直接不调用这个方法
所以如果要监听上边这个回调,那么在返回数据的时候就得处理下,如果没有数据了,就返回一个size为0的集合,而不是不调用callback.onResult(list)方法
网友评论