如何使用 kotlin 实现分页

Posted

技术标签:

【中文标题】如何使用 kotlin 实现分页【英文标题】:How to implement pagination with kotlin 【发布时间】:2022-01-02 20:54:34 【问题描述】:

您好,最近我将我的项目从 java 切换到 kotlin,我是 kotlin 的初学者,我想在我的回收站视图中实现分页

recycler 视图包含存储在 firebase 中的图像,这是我想要的

选项 1 是首先在创建片段时加载 20 张图片,当用户到达最后一张图片 (20) 时,它会在回收站视图中加载接下来的 20 张图片

选项 2

(可选,但希望在我的应用中使用这种分页)

你见过 Pinterest 和 Instagram,如果你只是正常滚动,你可能看不到图像正在加载,直到你滚动非常快,然后它会显示进度条?

例如:Pinterest

1:正常滚动或慢速滚动Link

2:滚动非常快Link

如果可能的话,我想要这种功能

我认为 Instagram 或 Pinterest 中正在发生的事情是为了例如

他们在开始时可能会加载 20 张图片,当用户到达第 10 张图片时(仍有 10 张图片可供用户查看),他们只会加载接下来的 10 张图片,因此它只使用 20 张图片来维护适配器,而不是到达列表末尾以加载下一个图像(因此,如果用户滚动非常快,这将是一个例外)


注意:我只是不知道分页是什么意思还是别的什么 也不确定我说的是我使用的技术 推测

代码

Home_Fragment.kt

我只包含了所需的代码,但如果您需要更多参考 请告诉我,我将通过实施的分页更新问题(不知道是分页还是其他,但它不起作用)

val staggeredGridLayoutManager =
            StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL)
        postRecyclerView.layoutManager = staggeredGridLayoutManager
        postRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() 
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) 
                visibleItemCount = staggeredGridLayoutManager.childCount
                totalItemCount = staggeredGridLayoutManager.itemCount
                val firstVisibleItems: IntArray? = null
                staggeredGridLayoutManager.findFirstVisibleItemPositions(firstVisibleItems)
                if (loading) 
                    if (totalItemCount > previousTotal) 
                        loading = false
                        previousTotal = totalItemCount
                    
                
                if (!loading && totalItemCount - visibleItemCount
                    <= firstVisibleItem + visibleThreshold
                ) 
                    data
                    loading = true
                
            
        )
        data
        //        setupFirebaseAuth();
        shimmerFrameLayout!!.startShimmer()
        mUploads = ArrayList()
        postsAdapter = PostAdapter_Home(requireContext(), mUploads)
        postRecyclerView.adapter = postsAdapter
        postRecyclerView.scrollToPosition(saved_position)
        //        postsAdapter.stateRestorationPolicy = PREVENT_WHEN_EMPTY;
        return view
    
 private val data: Unit
        get() 
            databaseReference.addValueEventListener(object : ValueEventListener 
                @SuppressLint("NotifyDataSetChanged")
                override fun onDataChange(snapshot: DataSnapshot) 
                    if (snapshot.exists()) 
                        shimmerFrameLayout!!.stopShimmer()
                        shimmerFrameLayout!!.visibility = View.GONE
                        postRecyclerView.visibility = View.VISIBLE
                        mUploads!!.clear()
                        for (dataSnapshot in snapshot.children) 
                            val upload = dataSnapshot.getValue(Upload::class.java)!!
                            upload.setmKey(dataSnapshot.key)
                            mUploads!!.add(upload)
                        
                    
                    postsAdapter!!.setUploads(mUploads)
                    //notify the adapter
                    postsAdapter!!.notifyItemRangeInserted(0, mUploads!!.size)
                    loading = true
                

                override fun onCancelled(error: DatabaseError) 
                    loading = true
                
            )
        

PostAdapter_Home.kt

class PostAdapter_Home(var mcontext: Context, var mUploads: MutableList<Upload?>?) :
    RecyclerView.Adapter<PostAdapter_Home.PostViewHolder>() 
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder 
        val view: View
        view = LayoutInflater.from(mcontext)
            .inflate(R.layout.post_item_container_home_ex, parent, false)
        return PostViewHolder(view)
    

    override fun onBindViewHolder(holder: PostViewHolder, position: Int) 
        val shimmer = ColorHighlightBuilder()
            .setBaseColor(Color.parseColor("#F3F3F3"))
            .setBaseAlpha(1f)
            .setHighlightColor(Color.parseColor("#E7E7E7"))
            .setHighlightAlpha(1f)
            .setDropoff(50f)
            .build()
        val shimmerDrawable = ShimmerDrawable()
        shimmerDrawable.setShimmer(shimmer)
        val uploadCurrent = mUploads?.get(position)
        Glide.with(mcontext)
            .load(uploadCurrent?.getmImageUrl())
            .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
            .placeholder(shimmerDrawable)
            .fitCenter()
            .into(holder.imageView)

    

    
    override fun getItemCount(): Int 
        return mUploads?.size!!
    

    //    public long getId() 
    //        return this.id;
    //    
    //
    //    @Override
    //    public long getItemId(int position) 
    //        return mUploads.get(position).Id;
    //    
    fun setUploads(uploads: MutableList<Upload?>?) 
        mUploads = uploads
    

    class PostViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) 
        val imageView: ShapeableImageView

        init 
            imageView = itemView.findViewById(R.id.imagePostHome)
        
    

上传.kt

package com.example.myappnotfinal.AdaptersAndMore

import com.google.firebase.database.Exclude

class Upload 
    private var mImageUrl: String? = null
    private var mKey: String? = null

    constructor() 
    constructor(imageUrl: String?) 
        mImageUrl = imageUrl
    

    fun getmImageUrl(): String? 
        return mImageUrl
    

    fun setmImageUrl(mImageUrl: String?) 
        this.mImageUrl = mImageUrl
    

    @Exclude
    fun getmKey(): String? 
        return mKey
    

    @Exclude
    fun setmKey(Key: String?) 
        mKey = Key
    

【问题讨论】:

android 有分页库,任何自定义实现都顾名思义,自定义。所以,不要问如何做整个事情,而是在当时解决 1 个问题 developer.android.com/topic/libraries/architecture/paging/… 好的,但至少告诉我我提到的第一个选项的解决方案,当项目到达末尾时,图像会在哪里加载,顺便说一句,我尝试实现分页库,但它给了我错误“不能获取 org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler 类型对象的未知属性“实现”。“ 我做到了,分页库做到了,那个错误是另一个问题。 ok 我已经添加了分页库,如何实现我在问题中提到的第一个选项 您是否要一次加载所有的 Firebase 项目,并希望对它们进行分页?还是你想先调用 Realtime database 20,然后当你到达 recyclerview 的末尾时,再加载 20 个? 【参考方案1】:

最好将 Android 的分页库与PagingDataAdapter 一起用于 RV, 您可以在线阅读 Pro 和 Con,

它解决了你的问题

    加载分页数据

    因为它之前会加载下一页,所以您不必担心滚动的慢和快

你需要创建一个 PagingSource 来加载数据

class PagingSource<T: BaseModel> (
    private val endPoint : suspend (Int) -> ApiResult<PageResult<T?>>,
    private val filters : suspend (T) -> Boolean = true
) : PagingSource<Int ,T>() 

    companion object
        const val DEFAULT_STARTING_PAGE_INDEX = 0
    

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> 
        Timber.d("load Called for : $endPoint::class")
        val pageIndex = params.key ?: DEFAULT_STARTING_PAGE_INDEX
        val responsePageable = endPoint(pageIndex)
        val responseData : MutableList<T> = mutableListOf()
        val nextKey: Int?
        when( responsePageable ) 
            is ApiResult.Success -> 
                if(responsePageable.data.content.isNotEmpty()) 
                    nextKey = pageIndex + 1
                    responseData.addAll(responsePageable.data.content
                        .filterNotNull()
                        .filter  filters(it) 
                    )
                
                else 
                    nextKey = null
                
            
            is ApiResult.NetworkError -> 
                return LoadResult.Error(
                    Throwable("No network connection!")
                )
            
            else -> 
                nextKey = null
            
        

        return LoadResult.Page(
            data = responseData ,
            prevKey = if(pageIndex == DEFAULT_STARTING_PAGE_INDEX) null else pageIndex ,
            nextKey = nextKey
        )
    

    /**
     * The refresh key is used for subsequent calls to PagingSource.Load after the initial load.
     */
    override fun getRefreshKey(state: PagingState<Int, T>): Int? 
        // We need to get the previous key (or next key if previous is null) of the page
        // that was closest to the most recently accessed index.
        // Anchor position is the most recently accessed index.
        return state.anchorPosition?.let  anchorPosition ->
            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
        
    


以上是我对分页源ref的自定义实现

它使用ApiResult 作为响应包装器,您可以将其替换为Response(Retrofit Default)或relevent API_RESULT files

以上是PagingSource的使用示例

fun getCoreCharacters(
        sortParam: Character.Companion.SortCharacter ,
        filters: suspend (Character) -> Boolean
    ) : Flow<PagingData<Character>>
        return Pager(
            config = PagingConfig(
                pageSize = DEFAULT_PAGE_SIZE,
                enablePlaceholders = false
            ),
            pagingSourceFactory = 
                PagingSource(endPoint = loadMore@ x: Int ->
                    characterApi.getCoreCharacters(x ,sortParam.value)
                ,
                filters = filters
                )
            
        ).flow
    

ref

【讨论】:

好的,所以我必须创建一个类 PagingSource,然后我必须在我的 oncreate 视图中添加有趣的 getCoreCharacters,对吗? 在我的代码中,上面的代码会有什么变化? @VasantRaval 观看 this 他几乎解释了一切,如果是,请关闭问题。 感谢兄弟的信息,但如果你告诉我如何用我的代码做到这一点,那将非常有帮助,这将非常有帮助,谢谢,不着急你可以慢慢来你想要

以上是关于如何使用 kotlin 实现分页的主要内容,如果未能解决你的问题,请参考以下文章

如何从分页源或远程调解器 Kotlin 调用不同的 api 资源

提供Kotlin阵列的补偿 .take(n:Int)。这是分页的好方法吗?

Kotlin recyclerview 分页重启(Pagination)

如何在 Kotlin 中使用 PKCE 实现 Spotify 授权代码

如何在 kotlin Multiplatform 和 Swift 中使用默认接口实现

如何使用 UIScrollViewDelegate 方法重新实现 UIScrollView 分页