RecyclerView-Selection

构建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需要的参数

  1. selectionId 一个字符串,用来在Activity或Fragment中标识我们的选择项
  2. recyclerView 需要应用跟踪器的RecyclerView
  3. keyProvider 选择键的来源
  4. detailsLookup RecyclerView items的详细信息
  5. 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容