如何使用 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 授权代码