首页 Android 正文
  • 本文约2802字,阅读需14分钟
  • 110
  • 0

Kotlin Jetpack Paging3 和Flow结合使用注意点

摘要

在用DataBinding时要注意DataBinding访问的是静态方法 在Kotlin中就要用companion object和@JvmStatic class ImageViewBindingAdapter { //里面的BindingAdapter方法必须是静态方法,否则会编译会报错 //DataBinding调用必须是静态方法 companion o...

在用DataBinding时要注意DataBinding访问的是静态方法

在Kotlin中就要用companion object和@JvmStatic

class ImageViewBindingAdapter {
    //里面的BindingAdapter方法必须是静态方法,否则会编译会报错
    //DataBinding调用必须是静态方法
    companion object {
        @JvmStatic
        @BindingAdapter("image")
        fun setImage(imageView:ImageView, url: String){
            if (!TextUtils.isEmpty(url)){
                //加载网络图片
            } else{
                imageView.setBackgroundColor(Color.GRAY)
            }
        }
    }
}

Paging3分页数据错乱的问题

在计算paging的prevKey和nextKey,也就是上一页,下一页的时候,需要考虑PagingConfig中的initialLoadSize参数

fun loadMovie(): Flow<PagingData<Movie>> {
    return Pager(
        config = PagingConfig(
            pageSize = 8,
            //第一次加载的数量,16也就是2页,默认是3*pageSize
            initialLoadSize = 16
        ),
        pagingSourceFactory = { MoviePagingSource() }
    ).flow
}

class MoviePagingSource: PagingSource<Int, Movie>() {
    override fun getRefreshKey(state: PagingState<Int, Movie>): Int? {
        //第一次加载
        return 1
    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> {
        val currentPage = params.key ?: 1
        val pageSize = params.loadSize
        val movies = RetrofitClient.createApi(MoviesApi::class.java).getMovies(currentPage, pageSize)

        var prevKey: Int? = null
        var nextKey: Int? = null

        //PagingConfig中的2个参数
        val realPageSize = 8
        val initialLoadSize = 16
        if (currentPage == 1){
            prevKey = null
            nextKey = initialLoadSize / realPageSize + 1
        }else{
            prevKey = currentPage - 1
            nextKey = if (movies.hasMore) currentPage + 1 else null
        }

        //下面这样计算,在initialLoadSize不等于realPageSize的时候会有数据错乱
        //prevKey = if (currentPage == 1) null else currentPage - 1
        //nextKey = if (movies.hasMore) currentPage + 1 else null

        return try {
            LoadResult.Page(
                data = movies.movieList,
                prevKey = prevKey,
                nextKey = nextKey
            )
        }catch (e: Exception){
            e.printStackTrace()
            return LoadResult.Error(e)
        }
    }

}

给Paging加上拉加载更多

recycleView.adapter = movieAdapter.withLoadStateFooter(MovieLoadMoreAdapter(this@MainActivity))

只需要加个适配器就可以

class MovieLoadMoreAdapter(private val context: Context): LoadStateAdapter<BindViewHolder>() {
    override fun onBindViewHolder(holder: BindViewHolder, loadState: LoadState) {
    }

    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): BindViewHolder {
        val binding = MovieLoadmoreBinding.inflate(LayoutInflater.from(context), null, false)
        return BindViewHolder(binding)
    }
}

Paging加上下拉刷新

布局加上SwipeRefreshLayout

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    android\:id="@+id/swipeRefreshLayout"
    android\:layout\_width="match\_parent"
    android\:layout\_height="match\_parent">

     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/recycleView"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

 </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

加上刷新监听

swipeRefreshLayout.setOnRefreshListener {   movieAdapter.refresh()}

监听刷新结束

lifecycleScope.launchWhenCreated {
    movieAdapter.loadStateFlow.collectLatest { state ->
        mBinding.swipeRefreshLayout.isRefreshing = state.refresh is LoadState.Loading
    }
}

下拉刷新后,底部上拉加载更多的loadmore的动画不显示

  • PageConfig还有一个属性是prefetchDistance,预刷新的距离,距离最后一个item多远时加载数据,默认为pageSize

  • 当prefetchDistance很小,并且initialLoadSize也很小时,就会出现上面的bug。比如initialLoadSize=8,prefetchDistance=1时

  • 解决办法也比较简单,2个属性设置的大一点就行了

APP横竖屏切换之后paging加载的数据没有缓存起来

  • ViewModel缓存数据要在属性中

  • 还有就是Paging返回的是flow,需要用cachedIn(viewModelScope)来让Paging的flow的生命周期和ViewModelScope的生命周期保持一致,也就是和activity保持一致

class MovieViewModel: ViewModel() {

    private val movies by lazy {
        Pager(
            config = PagingConfig(
                pageSize = 8,
                //第一次加载的数量,16也就是2页
                initialLoadSize = 16,
                //预刷新的距离,距离最后一个item多远时加载数据,默认为pageSize
                prefetchDistance = 8
            ),
            pagingSourceFactory = { MoviePagingSource() }
        ).flow.cachedIn(viewModelScope)
    }

    fun loadMovie(): Flow<PagingData<Movie>> = movies

}

扫描二维码,在手机上阅读


    评论