首先看下效果图,用的是tablayout和viewpager,加载了3个fragment
<android.support.design.widget.TabLayout
android:id="@+id/tab_page"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<android.support.v4.view.ViewPager
android:id="@+id/vp_page"
android:layout_width="match_parent"
android:layout_height="match_parent" />
activity如下
class ActivityPagingFragments:BaseActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_paging_fragments)
defaultSetTitle("page 3 data source")
vp_page.adapter=MyVpAdapter(supportFragmentManager)
tab_page.setupWithViewPager(vp_page)
}
var titles= arrayOf("PageKeyed","ItemKeyed","Positional")
inner class MyVpAdapter(fragmentManager: FragmentManager):FragmentPagerAdapter(fragmentManager){
override fun getItem(position: Int): Fragment {
when(position){
0->{
return FragmentPageKeyedDS()
}
1->{
return FragmentItemKeyedDS()
}
2->{
return FragmentPositionalDS()
}
else ->{
return Fragment()
}
}
}
override fun getCount(): Int {
return titles.size
}
override fun getPageTitle(position: Int): CharSequence? {
return titles[position]
}
}
}
效果图
image.png因为3个fragment除了DataSource其他都一样,就把逻辑写在基类了。
recyclerview设置一个PagedListAdapter【带一个ItemCallback的,用来判定是不是同一个item而已】
数据加载的核心方法就是makePageList 里,build的一个LivePagedListBuilder,完事添加observer,获取到数据以后,通过adapter的submitList提交更新。
里边有个CountDownLatch,主要是为了让fragment可见的时候才加载数据。所以用了下边的线程
,因为CountDownLatch执行await会阻塞线程,等待状态,在onActivityCreate和setUserVisibleHint判断fragment可见之后,countdownlatch才可以继续往下进行,也就是调用makePageList加载数据
fun loadDataNow(){
Thread{
countDownLatch?.await()
makePageList()
countDownLatch=null
}.start()
}
open abstract class FragmentPageBase:BaseFragment(){
override fun getLayoutID(): Int {
loadDataNow()
return R.layout.fragment_page_default
}
private fun makePageList() {
if(isAdded)
LivePagedListBuilder(MyDataSourceFactory(), getPageConfig()).build().observe(this, Observer {
println("base 34==================observer====${it?.size}")
getAdapter().submitList(it)
})
}
inner class MyDataSourceFactory:DataSource.Factory<Int,Student>(){
override fun create(): DataSource<Int, Student> {
return getDataSource()
}
}
public fun getPageConfig():PagedList.Config{
return PagedList.Config.Builder()
.setPageSize(8) //分页数据的数量。在后面的DataSource之loadRange中,count即为每次加载的这个设定值。
.setPrefetchDistance(8) //提前多少数据开始继续加载数据。如果是上滑的话对应的就是离最后个item还有2个位置的时候开始记载更多数据。下拉一样的道理
.setInitialLoadSizeHint(8)
.setEnablePlaceholders(false)
.build()
}
private fun getAdapter(): MyPagingAdapter {
return rv_default.adapter as MyPagingAdapter
}
public abstract fun getDataSource():DataSource<Int,Student>
var countDownLatch:CountDownLatch?=CountDownLatch(2)
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
countDownLatch?.countDown()
rv_default.apply {
layoutManager= LinearLayoutManager(activity)
//弄一条灰色的间隔线
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)
}
}
fun loadDataNow(){
Thread{
countDownLatch?.await()
makePageList()
countDownLatch=null
}.start()
}
override fun onDestroy() {
super.onDestroy()
countDownLatch?.run {
repeat(this.count.toInt()){
this.countDown()
}
}
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
println("${javaClass.name} setUserVisibleHint=============$isVisibleToUser")
if(isVisibleToUser){
countDownLatch?.countDown()
}
super.setUserVisibleHint(isVisibleToUser)
}
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
}
}
}
下边分析3种datasource
PageKeyedDataSource
/**
* PageKeyedDataSource分析说明,它的数据只会加载一次,来回滑动的话不会再次加载。
* 我们平时如果分页用的是pageNum和pageSize的话用这个比较合适
* */
class FragmentPageKeyedDS:FragmentPageBase(){
override fun getDataSource(): DataSource<Int, Student> {
return object :PageKeyedDataSource<Int,Student>(){
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Student>) {
println("loadInitial size ===: ${params.requestedLoadSize} ")
getDataBackground(5,params.requestedLoadSize).apply {
callback.onResult(this,4,6)
//这里的previousPageKey,和nextPageKey决定了前后是否有数据,如果你传个null,那么就表示前边或者手边没有数据了。也就是下边的loadBefore或者LoadAfter不会执行了
}
}
//往上滑动会不停的调用这个方法,往回滑动的时候不调用任何方法
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Student>) {
println("loadAfter size =======: ${params.requestedLoadSize} page:${params.key}")
getDataBackground(params.key,params.requestedLoadSize).let {
callback.onResult(it,params.key+1)
}
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Student>) {
println("loadBefore size====: ${params.requestedLoadSize} page:${params.key}")
if(params.key<0){
return
}
getDataBackground(params.key,params.requestedLoadSize).let {
callback.onResult(it,params.key-1)
}
}
}
}
fun getDataBackground(page:Int,size :Int): List<Student>{
println("FragmentPageKeyedDS getData=====================${Thread.currentThread().name}") //打印的结果是2个线程来回切换pool-4-thread-1,pool-4-thread-2
var lists= arrayListOf<Student>()
var startPosition=page*size
repeat(size){
lists.add(Student(startPosition+it+1,"stu ${startPosition+it+1}"))
}
return lists
}
}
首次加载肯定走loadInitial方法,完事callback回调的方法
public abstract void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
@Nullable Key nextPageKey);
里边的previousPageKey和nextPageKey分别决定了前边后边是否有数据
previousPageKey这个值是给首次调用loadBefore方法用的,里边params的key就是这个值。
然后loadBefore这个方法里最后的回调callback.onResult(it,params.key-1),第二个参数,就决定了下次调用这个loadBefore里params的key值
loadAfter一样的道理。
可以看到这里的key都是由上一次的数据来指定的。
所以我们测试初始化用了个key=5,完事前后就用4和6,4又指定了3,2,1。。
比如这里的key你也可用用26个英文字母了。比如初始化指定了一个m,你可以在loadInitial里的callback.onResult(this,"l","n"),然后loadAfter你可以根据当前的字母,指定下一个loadAfter要用的字母。。
ItemKeyedDataSource
这个加载数据的时候,如果是上拉的时候那么靠最后一条数据返回的key来决定,如果是下拉的话,是根据最顶上的一条数据来决定的
这个用来处理记载聊天记录最方便了。
默认加载数据库保存的最后比如10条记录展示,完事等同步完网络未读消息以后,就可以根据最后一条数据的id,从数据库里查询之后的最新数据了。如果我们往下拉,看历史数据,那么也可以跟具最顶上一条数据的id,从数据库里查比它更老的 数据拉。
/**ItemKeyedDataSource
* 这个在初始化加载了默认的比如10条数据以后,它会先执行loadBefore方法加载初始化的首条数据之前的数据【如果不需要,需要自己在方法里处理】,完事会执行
* loadAfter加载初始化的最后一条数据之后的数据
* 如果接口分页使用的最后一条数据的id之类的那么用这个比较合适
* */
class FragmentItemKeyedDS:FragmentPageBase(){
override fun getDataSource(): DataSource<Int, Student> {
return object :ItemKeyedDataSource<Int,Student>(){
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Student>) {
println("FragmentItemKeyedDS loadInitial size =====: ${params.requestedLoadSize} ")
getDataBackgroundAfter(0,params.requestedLoadSize)?.apply {
callback.onResult(this,0,this.size)
}
}
//刚开始是在loadInitial>> loadBefore>>然后就是这个了,之后手指上滑的时候执行这个
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Student>) {
println("FragmentItemKeyedDS loadAfter size =======: ${params.requestedLoadSize} page:${params.key}")
getDataBackgroundAfter(params.key,params.requestedLoadSize)?.let {
callback.onResult(it)
}
}
//这个在loadInitial加载完pagesize的数据以后,会先调用这个加载第一条数据之前的数据
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Student>) {
println("FragmentItemKeyedDS loadBefore size====: ${params.requestedLoadSize} page:${params.key}")
getDataBackgroundBefore(params.key,params.requestedLoadSize)?.let {
callback.onResult(it)
}
}
//这里返回的key就是上边方法里LoadParams的key
override fun getKey(item: Student): Int {
println("FragmentItemKeyedDS=================getKey=======${item}")
return item.id
}
}
}
fun getDataBackgroundAfter(startPosition:Int,size :Int): List<Student>?{
println("FragmentItemKeyedDS getData=====================${Thread.currentThread().name}")
var lists= arrayListOf<Student>()
if(startPosition>50){
return null
}
repeat(size){
lists.add(Student(startPosition+it+1,"stu ${startPosition+it+1}"))
}
return lists
}
fun getDataBackgroundBefore(startPosition:Int,size :Int): List<Student>?{
println("FragmentItemKeyedDS getData=====================${Thread.currentThread().name}")
var lists= arrayListOf<Student>()
if(startPosition<-50){//我们初始化的数据就是1到8,第一条的key就是1,这里我不需要在1之前还有数据,所以就return了,根据实际情况来处理
return null
}
repeat(size){
lists.add(0,Student(startPosition-it-1,"stu ${startPosition-it-1}"))
}
return lists
}
}
PositionalDataSource
最后这种,就和他名字里的positional一样,它查询数据都是靠position的
而且,也可以看到,这个的类只有value一个泛型,如下,他的key已经固定是int拉
public abstract class PositionalDataSource<T> extends DataSource<Integer, T>
另外loadRange方法的参数LoadRangeParams的这个startPosition,这个最小是0
这个就是根据position来的,主要用来处理固定数量的数据,可以任意获取某段的数据。比如有100条数据,我可以从第20条数据开始获取。
class FragmentPositionalDS:FragmentPageBase(){
override fun getDataSource(): DataSource<Int, Student> {
return object :PositionalDataSource<Student>(){
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Student>) {
println("FragmentPositionalDS=====load range ${params.startPosition}==${params.loadSize}===${Thread.currentThread().name}")
getDataBackground(params.startPosition,params.loadSize)?.apply {
callback.onResult(this)
}
}
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Student>) {
println("FragmentPositionalDS=====loadInitial ${params.requestedStartPosition}===${params.requestedLoadSize}==${params.pageSize}==${Thread.currentThread().name}")
getDataBackground(0,params.pageSize)?.apply {
callback.onResult(this,0)
}
}
}
}
fun getDataBackground(startPosition:Int,size :Int): List<Student>?{
println("FragmentPositionalDS getData=====================${Thread.currentThread().name}")
var lists= arrayListOf<Student>()
if(startPosition>90){
return null
}
repeat(size){
lists.add(Student(startPosition+it+1,"stu ${startPosition+it+1}"))
}
return lists
}
}
举例比如loadInitial里callback.onResult(this,20)这第二个参数改为20,表示我们首次加载的数据是从position为20开始的,那么日志如下
看下第三第四行,我们首次加载的position是20,而基类里设置了pageSize是8,
所以结果就是它往前加载了8条【20-8=12开始】,往后加载了8条【20+8=28开始】
至于第一条打印的那个requestedStartPosition是0,因为这个我们没有设置,反正也没用到,如果你要用这个,让他是20,也可以的,如下代码的地方可以初始化这个key的
LivePagedListBuilder(MyDataSourceFactory(), getPageConfig()).setInitialLoadKey(20).build()
System.out: FragmentPositionalDS=====loadInitial 0===8==8==pool-6-thread-2
System.out: base 34==================observer====8
System.out: FragmentPositionalDS=====load range 12==8===pool-6-thread-1
System.out: FragmentPositionalDS=====load range 28==8===pool-6-thread-2
最后简单总结下
这3种其实也差不多,就是key不太一样,都是可以前后加载数据的。
突然想起万一要下拉刷新咋办,好像没见人写过。
就自己摸索写个,也不知道对不对,就用的系统的SwipeRefreshLayout
反正就是先把adapter里的pagelist置空,然后重新设置pagelist。也就是重新初始化一遍。
功能是实现了,至于好不好,等以后看到别人写的再来修改
srf.apply {
setOnRefreshListener{
getAdapter().submitList(null)
makePageList()
isRefreshing=false
}
}
如果你用到了setBoundaryCallback,那么DataSource的代码需要稍微修改下,详情参考下帖最后部分
https://www.jianshu.com/p/57714f99c55d
网友评论