这是一个在 Kotlin 中似乎可行的解决方案。我基于https://gist.github.com/nesquena/d09dc68ff07e845cc622 中的一些代码。
事实证明,如果您调用回收器适配器的 notifyItemRemoved() 和 notifyItemInserted() 方法(对于您看不到的视图),它不会移动您当前正在查看的视图的位置,但它会改变 RecyclerView 感知到的在你的右边和左边。
我得到了这个工作,所以我不妨分享一下结果:这个回收器适配器假装你展示的同一组项目有 5 个副本,并且每当显示左侧或右侧最远副本之一的项目时,它使用 adapter.notifyItemRemoved() 和 notifyItemInserted() 将 RecyclerView 的感知转移到正在滚动的一侧。
您可能需要对此进行一些测试。滚动几秒钟似乎对我来说很好。为了安全起见,我总共放了 5 个副本(1 个在中间,两个在周围),但我敢打赌,如果你稍微弄乱了它,这将适用于 3 个副本。 :)
package com.github.ajsnarr98.testingplayground
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.github.ajsnarr98.testingplayground.databinding.ActivityCircularBinding
class CircularActivity : AppCompatActivity() {
private val list = listOf<Int>(1,2,3,4,5,6,7,8,9,10,11,12)
lateinit var binding: ActivityCircularBinding
lateinit var recyclerAdapter: CircularAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCircularBinding.inflate(layoutInflater)
setContentView(binding.root)
recyclerAdapter = CircularAdapter(list, binding.list)
binding.list.apply {
adapter = recyclerAdapter
layoutManager = LinearLayoutManager(this@CircularActivity, RecyclerView.HORIZONTAL, false)
}
recyclerAdapter.init()
}
}
package com.github.ajsnarr98.testingplayground
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class CircularAdapter(
private val items: List<Int>,
private val recyclerView: RecyclerView,
) : RecyclerView.Adapter<CircularAdapter.ViewHolder>() {
var offset: Int = 0
var numSets = 5
val initialPosition = items.size * 2 // two sets of items before, two sets after
fun init() {
recyclerView.scrollToPosition(initialPosition)
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
// This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish.'
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
// this example only supports LinearLayoutManager
val layoutManager: LinearLayoutManager = recyclerView.layoutManager as? LinearLayoutManager ?: return
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
if (isInBoundarySet(firstVisibleItemPosition)) {
insertItemsLeft()
}
if (isInBoundarySet(lastVisibleItemPosition)) {
insertItemsRight()
}
}
})
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_number, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(itemForPosition(position))
}
override fun getItemCount(): Int = items.size * numSets
/**
* Returns true if the given position is within items.size from 0 or
* getItemCount().
*/
private fun isInBoundarySet(position: Int): Boolean {
return position + items.size >= itemCount || position - items.size < 0
}
private fun itemForPosition(position: Int) = items[position % items.size]
/**
* Pretends a new set of items was inserted to the right, and a set was was removed to the left.
*/
fun insertItemsRight() {
numSets--
notifyItemRangeRemoved(0, items.size)
numSets++
notifyItemRangeInserted(itemCount, items.size)
}
/**
* Pretends a new set of items was inserted to the left, and a set was was removed to the right.
*/
fun insertItemsLeft() {
numSets--
notifyItemRangeRemoved(itemCount-items.size, items.size)
numSets++
notifyItemRangeInserted(0, items.size)
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: Int) {
if (this.itemView is TextView) {
this.itemView.text = item.toString()
}
}
}
companion object {
private const val NUMBER_VIEW = 0
}
}
item_number.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@drawable/frame"
android:textAlignment="center"
android:textStyle="bold"
android:textSize="40sp"
tools:text="1"/>
activity_circular.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CircularActivity">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Circular recycler view"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/list"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
frame.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<!-- View background color -->
<solid
android:color="@color/white" >
</solid>
<!-- View border color and width -->
<stroke
android:width="3dp"
android:color="@color/black" >
</stroke>
</shape>