RecyclerView
小部件是当今大多数Android应用程序不可或缺的一部分。 自从2014年末将其添加到Android支持库以来,它已经使ListView
小部件黯然失色,成为显示大型复杂列表的首选小部件。 但是,它缺少一个重要功能:支持选择和跟踪列表项。 Google在今年3月发布的附加库RecyclerView Selection试图解决此问题。
在本教程中,我将向您展示如何使用新库来创建一个应用程序,该应用程序提供了用于选择列表中多个项目的直观界面。 跟随这个Android RecyclerView多项选择示例,您将学到一些可以在自己的应用中应用的技能。
先决条件
要继续进行,您需要:
- 最新版本的Android Studio
- 运行Android API级别23或更高版本的设备或模拟器
要将RecyclerView Selection库添加到您的Android Studio项目中,请在app
模块的build.gradle文件中提及以下implementation
依赖项 :
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.support:recyclerview-selection:28.0.0'
在整个教程中,我们将处理一小部分项目,每个项目都包含一个人的姓名和电话号码。
要存储每个列表项的数据,请创建一个名为Person
的Kotlin数据类,并向其中添加两个属性: name
和phone
。
data class Person(val name:String,
val phone: String)
现在,您可以继续在主要活动中创建Person
对象的列表。
val myList = listOf(
Person("Alice", "555-0111"),
Person("Bob", "555-0119"),
Person("Carol", "555-0141"),
Person("Dan", "555-0155"),
Person("Eric", "555-0180"),
Person("Craig", "555-0145")
)
当然,我们将使用RecyclerView
小部件来显示列表。 因此,在主活动的布局XML文件中添加一个<RecyclerView>
标记。
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/my_rv">
</android.support.v7.widget.RecyclerView>
要指定列表项的布局,请创建一个新的XML文件并将其命名为list_item.xml 。 在其中添加两个TextView
小部件:一个用于显示名称,另一个用于显示电话号码。 如果使用LinearLayout
元素定位小部件,则XML文件的内容应如下所示:
<LinearLayout
xmlns:android="https://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/list_item_name"
style="@style/TextAppearance.AppCompat.Large"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/list_item_phone"
style="@style/TextAppearance.AppCompat.Small"/>
</LinearLayout>
您可以将视图持有者视为一个对象,其中包含对列表项布局中存在的视图的引用。 没有它, RecyclerView
小部件将无法有效地呈现列表项。
现在,您需要一个视图保持器,其中包含您在上一步中创建的两个TextView
小部件。 因此,创建一个扩展RecyclerView.ViewHolder
类的新类,并初始化对其中两个小部件的引用。 这是如何做:
class MyViewHolder(view: View)
: RecyclerView.ViewHolder(view) {
val name: TextView = view.list_item_name
val phone: TextView = view.list_item_phone
// More code here
}
此外, RecyclerView
Selection
插件需要一种可以调用的方法来唯一地标识选定的列表项。 理想情况下,此方法属于视图持有者本身。 此外,它必须返回ItemDetailsLookup.ItemDetails
类的实例。 因此,将以下代码添加到视图持有人:
fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
object: ItemDetailsLookup.ItemDetails<Long>() {
// More code here
}
现在,您必须重写ItemDetails
类中存在的两个抽象方法。 首先,重写getPosition()
方法并返回其中的视图持有者的adapterPosition
属性。 adapterPosition
属性通常只是列表项的索引。
override fun getPosition(): Int = adapterPosition
接下来,重写getSelectionKey()
方法。 此方法必须返回可用于唯一标识列表项的键。 为了简单itemId
,我们只返回视图持有者的itemId
属性。
override fun getSelectionKey(): Long? = itemId
您可以*使用任何其他技术来生成选择键,只要它可以生成唯一的值即可。
为了使RecyclerView
Selection
插件正常工作,每当用户触摸RecyclerView
小部件时,您都必须将触摸的坐标转换为ItemDetails
对象。
创建一个扩展ItemDetailsLookup
类的新类,并向其添加一个可以接受RecyclerView
小部件作为参数的构造函数。 请注意,由于该类是抽象类,因此Android Studio会自动为其抽象方法生成一个存根。
class MyLookup(private val rv: RecyclerView)
: ItemDetailsLookup<String>() {
override fun getItemDetails(event: MotionEvent)
: ItemDetails<String>? {
// More code here
}
}
如您在上面的代码中看到的, getItemDetails()
方法接收一个MotionEvent
对象。 通过将事件的X和Y坐标传递给findChildViewUnder()
方法,可以确定与用户触摸的列表项关联的视图。 要将View
对象转换为ItemDetails
对象,您需要做的就是调用getItemDetails()
方法。 这是如何做:
val view = rv.findChildViewUnder(event.x, event.y)
if(view != null) {
return (rv.getChildViewHolder(view) as MyViewHolder)
.getItemDetails()
}
return null
现在,您需要一个可以将您的列表绑定到RecyclerView
小部件的适配器。 要创建一个类,请创建一个扩展RecyclerView.Adapter
类的新类。 因为适配器需要访问列表和活动的上下文,所以新类必须具有一个可以接受这两个参数的构造函数。
class MyAdapter(private val listItems:List<Person>,
private val context: Context)
: RecyclerView.Adapter<MyViewHolder>() {
}
重要的是,您必须明确指出此适配器的每个项目都将具有类型为Long
的唯一稳定标识符。 这样做的最佳位置是init
块内。
init {
setHasStableIds(true)
}
另外,为了能够使用项目的位置作为其唯一标识符,您必须重写getItemId()
方法。
override fun getItemId(position: Int): Long {
return position.toLong()
}
由于RecyclerView.Adapter
类是抽象的,因此您现在必须重写三个以上的方法才能使适配器可用。
首先,重写getItemCount()
方法以返回列表的大小。
override fun getItemCount(): Int = listItems.size
接下来,重写onCreateViewHolder()
方法。 此方法必须返回您在本教程前面创建的视图持有器类的实例。 要创建这样的实例,您必须调用该类的构造函数,并将列表项的膨胀布局传递给它。 要使布局膨胀,请使用LayoutInflater
类的LayoutInflater
inflate()
方法。 这是如何做:
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): MyViewHolder =
MyViewHolder(
LayoutInflater.from(context)
.inflate(R.layout.list_item, parent, false)
)
最后,重写onBindViewHolder()
方法并适当地初始化视图持有者中存在的两个TextView
小部件的text
属性。
override fun onBindViewHolder(vh: MyViewHolder, position: Int) {
vh.name.text = listItems[position].name
vh.phone.text = listItems[position].phone
}
此时,您几乎拥有呈现列表所需的一切。 但是,您仍然必须指定如何定位列表项。 现在,让我们使用LinearLayoutManager
实例将它们一个放在另一个下面。
为了获得最佳性能,我还建议您指出RecyclerView
小部件的大小在运行时不会改变。
将以下代码添加到您的主要活动中:
my_rv.layoutManager = LinearLayoutManager(this)
my_rv.setHasFixedSize(true)
最后,将适配器的新实例分配给RecyclerView
小部件的adapter
属性。
val adapter = MyAdapter(myList, this)
my_rv.adapter = adapter
如果您现在运行应用程序,则可以看到列表。
RecyclerView
小部件仍然不允许您选择任何项目。 要启用多项目选择,您的活动中将需要一个SelectionTracker
对象。
private var tracker: SelectionTracker<Long>? = null
您可以使用SelectionTracker.Builder
类初始化跟踪器。 向其构造函数传递的是选择ID, RecyclerView
小部件,**提供者,商品详细信息查找类和存储策略。
您可以随意使用任何字符串作为选择ID。 作为**提供者,可以使用StableIdKeyProvider
类的实例。
RecyclerView选择库提供了多种存储策略,所有这些策略都可以确保在用户设备旋转或资源紧缩期间Android系统关闭您的应用程序时,不会取消选择选定的项目。 现在,由于选择键的类型为Long
,因此必须使用Long
类型的StorageStrategy
对象。
Builder
准备就绪后,您可以调用其withSelectionPredicate()
方法来指定要允许用户选择的项目数。 为了支持多项目选择,作为方法的参数,必须传递createSelectAnything()
方法返回的SelectionPredicate
对象。
因此,在活动的onCreate()
方法内添加以下代码:
tracker = SelectionTracker.Builder<Long>(
"selection-1",
my_rv,
StableIdKeyProvider(my_rv),
MyLookup(my_rv),
StorageStrategy.createLongStorage()
).withSelectionPredicate(
SelectionPredicates.createSelectAnything()
).build()
为了充分利用存储策略,您必须始终尝试在onCreate()
方法中还原跟踪器的状态。
if(savedInstanceState != null)
tracker?.onRestoreInstanceState(savedInstanceState)
同样,您必须确保将跟踪器的状态保存在活动的onSaveInstanceState()
方法中。
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
if(outState != null)
tracker?.onSaveInstanceState(outState)
}
除非它与您的适配器关联,否则选择跟踪器不是很有用。 因此,通过调用setTracker()
方法将其传递给适配器。
adapter.setTracker(tracker)
setTracker()
方法尚不存在,因此在适配器类中添加以下代码:
private var tracker: SelectionTracker<Long>? = null
fun setTracker(tracker: SelectionTracker<Long>?) {
this.tracker = tracker
}
如果您此时尝试运行应用程序,则可以选择列表中的项目。 通过长按列表项进入多项目选择模式时,大多数设备上都会出现短暂的振动。 但是,由于当前无法将所选项目与未选中项区分开,因此您将没有视觉反馈。 要解决此问题,您需要在适配器的onBindViewHolder()
方法内进行一些更改。
突出显示所选项目的常规方法是更改其背景颜色。 因此,您现在必须更改项目的布局XML文件中存在的LinearLayout
小部件的背景颜色。 要获取对它的引用,请获取对视图保持器中可用的TextView
小部件之一的父级的引用。
在onBindViewHolder()
方法结尾之前添加以下代码:
val parent = vh.name.parent as LinearLayout
// More code here
接下来,可以调用SelectionTracker
对象的isSelected()
方法来确定是否SelectionTracker
了一项。
以下代码显示了如何将所选项目的背景颜色更改为青色:
if(tracker!!.isSelected(position.toLong())) {
parent.background = ColorDrawable(
Color.parseColor("#80deea")
)
} else {
// Reset color to white if not selected
parent.background = ColorDrawable(Color.WHITE)
}
如果您现在运行该应用程序,则应该能够看到您选择的项目。
通常,您想向用户显示当前选择了多少个项目。 使用RecyclerView Selection库,这样做非常容易。
通过调用addObserver()
方法,将SelectionObserver
对象与选择跟踪器addObserver()
。 在观察者的onSelectionChanged()
方法内部,您可以检测到所选项目数的变化。
tracker?.addObserver(
object: SelectionTracker.SelectionObserver<Long>() {
override fun onSelectionChanged() {
val nItems:Int? = tracker?.selection?.size()
// More code here
}
})
如何显示所选项目的数量取决于您。 现在,我建议您直接在活动的操作栏中显示数字。 (可选)您还可以更改操作栏的背景颜色,以使用户知道列表中有活动的选择。 以下代码向您展示了如何:
if(nItems!=null && nItems > 0) {
// Change title and color of action bar
title = "$nItems items selected"
supportActionBar?.setBackgroundDrawable(
ColorDrawable(Color.parseColor("#ef6c00")))
} else {
// Reset color and title to default values
title = "RVSelection"
supportActionBar?.setBackgroundDrawable(
ColorDrawable(getColor(R.color.colorPrimary)))
}
如果再次运行该应用程序,现在应该会看到标题更改,以反映您选择的列表项的数量。
结论
在本教程中,您学习了如何使用RecyclerView Selection加载项库向RecyclerView
小部件添加简单的项目选择支持。 您还学习了如何动态更改选定项目的外观,以便用户可以将其与未选定项目区分开。
要了解有关该库的更多信息,请参阅官方文档 。
翻译自: https://code.tutsplus.com/tutorials/how-to-add-selection-support-to-a-recyclerview--cms-32175