构建app
class MainActivity : AppCompatActivity() {
private val adapter = MainAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
adapter.list = createRandomIntList()
adapter.notifyDataSetChanged()
}
private fun createRandomIntList(): List<Int> {
val random = Random()
return (1..10).map { random.nextInt() }
}
}
class MainAdapter : RecyclerView.Adapter<MainAdapter.ViewHolder>() {
var list: List<Int> = arrayListOf()
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val number = list[position]
holder.bind(number)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater
.from(parent.context)
.inflate(R.layout.item_row, parent, false)
return ViewHolder(itemView)
}
override fun getItemCount(): Int {
return list.size
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private var text: TextView = view.findViewById(R.id.text)
fun bind(value: Int) {
text.text = value.toString()
}
}
}
继承RecyclerView-Selection
在app/build.gradle中添加如下代码
implementation 'androidx.recyclerview:recyclerview-selection:1.0.0'
选择一个key类型
选择一个key类型用来构建KeyProvider,selection库只支持三种类型:Parcelable, String and Long.
下面是对于该选用什么类型的建议:
Parcelable:任何Parcelable都可以用作selection的key,如果view中的内容与稳定的content:// uri相关联,你就使用uri作为你的key的类型
String:当基于字符串的稳定标识符可用时使用String
Long:当RecyclerView的long stable ID已经投入使用时,请使用Long,但是会有一些限制,在运行时访问一个稳定的id会被限定
我们将选择Long并使用列表项的位置作为其ID,首先我们需要id是稳定的,我们需要将setHasStableIds设置为true,将该选项设置为true只会告诉RecyclerView数据集中的每个项目都可以用Long类型的唯一标识符表示
init {
setHasStableIds(true)
{
现在我们使用item的pisition作为id,需要实现getItemId方法并且返回item的position作为id
override fun getItemId(position:Int):Long = position.toLong()
实现KeyProvider
现在我们选择了我们的密钥类型,接着实现我们的KeyProvider了。事实证明,选择库为我们提供了StableIdKeyProvider的实现
实现ItemDetailsLookup
这个类将为选择库提供有关与用户选择关联的项目的信息,该选择基于MotionEvent,所以我们必须映射到我们的ViewHolders,返回产生MotionEvent事件的item的信息
class MyItemDetailsLookup(private val recyclerView: RecyclerView):ItemDetailsLookup<Long>(){
override fun getItemDetails(event: MotionEvent): ItemDetails<Long>? {
val view = recyclerView.findChildViewUnder(event.x, event.y)
if (view != null) {
return (recyclerView.getChildViewHolder(view) as MainAdapter.ViewHolder)
.getItemDetails()
}
return null
}
}
要返回这些信息,我们需要在ViewHolder中创建一个新方法,该方法返回一个ItemDetails的实例
fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
object : ItemDetailsLookup.ItemDetails<Long>() {
override fun getPosition(): Int = adapterPosition
override fun getSelectionKey(): Long? = itemId
}
高亮选择的items
创建一个drawable用来根据item的状态来改变颜色
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/holo_blue_light" android:state_activated="true" />
<item android:drawable="@android:color/white" />
</selector>
然后将该drawable作为item的背景
android:background="@drawable/item_background"
在adapter中我们需要添加一个tracker字段,tracker就是用来让selection library跟踪用户的选择项,在MainActivity中创建该追踪器并且设置给adapter
var tracker: SelectionTracker<Long>? = null
我们还需要在viewholder中添加一个boolean参数用来告诉viewholder该position的item是否被用户选中
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val number = list[position]
tracker?.let {
holder.bind(number, it.isSelected(position.toLong()))
}
}
最后我们需要改变bind方法使用bool值来改变item的状态
fun bind(value: Int, isActivated: Boolean = false) {
text.text = value.toString()
itemView.isActivated = isActivated
}
创建tracker
到MainActivity中创建一个新的tracker,使用selection library提供的SelectionTracker.Builder。
一下是builder需要的参数
- selectionId 一个字符串,用来在Activity或Fragment中标识我们的选择项
- recyclerView 需要应用跟踪器的RecyclerView
- keyProvider 选择键的来源
- detailsLookup RecyclerView items的详细信息
- storage 选择状态的类型安全存储策略
除了我们之前创建的MyItemDetailsLookup之外,其他所有内容都由选择库提供。最后,我们将指定一个SelectionPredicate,允许选择多个项目而不受任何限制。
tracker = SelectionTracker.Builder<Long>(
"mySelection",
recyclerView,
StableIdKeyProvider(recyclerView),
MyItemDetailsLookup(recyclerView),
StorageStrategy.createLongStorage()
).withSelectionPredicate(
SelectionPredicates.createSelectAnything()
).build()
adapter.tracker = tracker
SelectionPredicates.createSelectAnything()允许我们选择多个item,你也可以创建自己的SelectionPredicate实现不同的抽象方法,返回true允许选择,false为不允许
return object : SelectionTracker.SelectionPredicate<K>() {
override fun canSetStateForKey(key: K, nextState: Boolean):
Boolean {
return true
}
override fun canSetStateAtPosition(position: Int, nextState:
Boolean): Boolean {
return true
}
override fun canSelectMultiple(): Boolean {
return true
}
}
选择观察者
现在我们有一个列表,我们可以选择多个项目,可以添加一个观察者来观察当前的选择项并做一些工作。
下面就是观察者代码,当选择项改变时,就会执行下面的代码
tracker?.addObserver(
object : SelectionTracker.SelectionObserver<Long>() {
override fun onSelectionChanged() {
super.onSelectionChanged()
val items = tracker?.selection!!.size()
if (items == 2) {
launchSum(tracker?.selection!!)
}
}
})
private fun launchSum(selection: Selection<Long>) {
val list = selection.map {
adapter.list[it.toInt()]
}.toList()
SumActivity.launch(this, list as ArrayList<Int>)
}
在生命周期事件中保持选择状态
如果我们旋转屏幕时所选择的item就会消失,事实上,如果旋转屏幕就会发生闪退:java.lang.NullPointerException: Attempt to invoke virtual method ‘int androidx.recyclerview.widget.RecyclerView$ViewHolder.getAdapterPosition()’ on a null object reference
这里我们创建自己的ItemKeyProvider替换掉从选择库中获取的StableIdKeyProvider
class MyItemKeyProvider(private val recyclerView: RecyclerView) :
ItemKeyProvider<Long>(ItemKeyProvider.SCOPE_MAPPED) {
override fun getKey(position: Int): Long? {
return recyclerView.adapter?.getItemId(position)
}
override fun getPosition(key: Long): Int {
val viewHolder = recyclerView.findViewHolderForItemId(key)
return
viewHolder?.layoutPosition ?: RecyclerView.NO_POSITION
}
}
原文地址:https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504