android - 如何用kotlin实现分页

标签 android kotlin android-recyclerview pagination

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

回收器 View 包含存储在 firebase 中的图像,这是我想要的

选项 1 是,首先在创建 fragment 时加载 20 个图像,当用户到达最后一个图像 (20) 时,它会在回收器 View 中加载接下来的 20 个图像

选项 2

(可选,但希望在我的应用程序中进行这种分页)

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

例如:Pinterest

1:正常或慢速滚动Link

2:滚动速度非常快Link

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

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

它们在开始时加载大约 20 个图像,当用户到达第 10 个图像时(还有 10 个图像等待用户查看),它们只加载接下来的 10 个图像,因此它只用 20 个图像维护适配器,而不是到达列表末尾以加载下一个图像(因此,如果用户滚动得非常快,这将是一个异常(exception))


Note: I just don't know what it is said pagination or something else and also not sure that what I said up are the technique used I'm just speculating

代码

Home_Fragment.kt

I only included the required code but if you want more references please tell me I will update the question btw the implemented pagination (don't know is it pagination or something else but it's not working)

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 的分页库与用于 RV 的 PagingDataAdapter 结合使用, 您可以在线阅读优点和缺点,

它解决了您的问题

  1. 加载分页数据

  2. 由于它之前加载下一页,因此您不必担心滚动的慢速和快速

您需要创建一个 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 (改进默认值)或 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

关于android - 如何用kotlin实现分页,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70096562/

相关文章:

android - 在android中长按和正常按下电源按钮

kotlin - 如何测试 lateinit var 是否从类外初始化? - Kotlin

Android Studio 不删除我在 Adapter 中的项目?

java - 发送 arraylist 值并在 php 中获取该数组值

Android Smack MessageEventListener

android - 在应用程序的生命周期中,伴随对象是否保留在内存中

android - 如何用 Any 类型打包值(value)?使用@Parcelize

android - 如何在列表中添加新项目时更新 RecyclerView 适配器数据

java - 应用程序没有采用我设置的布局 - 为什么?

android - 设备上 IntentFilter 分辨率的奇怪行为