关于第三个自写适配器,在其中必须实现记住所选元素的逻辑之后,我曾想过应该有一些已经包含所有必要内容的解决方案。 特别是在开发过程中,如果您不得不更改仅选择一项以进行多项选择的功能。
在研究了MVVM方法并将其完全浸入其中之后,上述问题就更加明显地出现了。 此外,适配器本身处于View
级别,而对于ViewModel
,有关所选元素的信息通常是极其必要的。
也许我没有花足够的时间在Internet上寻找答案,但是无论如何,我都没有找到现成的解决方案。 但是,在一个项目中,我提出了一个可能是通用的实现想法,所以我想分享一下。
备注 。 尽管Android上的MVVM在LiveData
的情况下进行实现是合乎逻辑的,但在现阶段,我还不准备使用它编写代码。 所以这只是为了未来。 但是最终的解决方案证明没有Android
依赖项,这有可能使它可以在kotlin可以运行的任何平台上使用。
选择管理器
为了解决此问题,编译了常规的SelectionManager
接口:
interface SelectionManager { fun clearSelection() fun selectPosition(position: Int) fun isPositionSelected(position: Int): Boolean fun registerSelectionChangeListener(listener: (position: Int, isSelected: Boolean) -> Unit): Disposable fun getSelectedPositions(): ArrayList<Int> fun isAnySelected(): Boolean fun addSelectionInterceptor(interceptor: (position: Int, isSelected: Boolean, callback: () -> Unit) -> Unit): Disposable }
默认情况下,已经有3种不同的实现:
MultipleSelection
该对象使您可以从列表中选择任意数量的元素;SingleSelection
一个对象仅允许您选择一个元素;NoneSelection
对象完全不允许选择元素。
可能是后者,所有问题中大多数都会出现,因此我将尝试在一个示例上进行展示。
转接头
应该通过构造函数将SelectionManager
对象作为依赖项添加到适配器。
class TestAdapter(private val selectionManager: SelectionManager) : RecyclerView.Adapter<TestHolder>() {
在示例中,我不会理会处理元素单击的逻辑,因此我们只同意持有人(无详细信息)完全负责单击侦听器的任命。
class TestHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(item: Any, onItemClick: () -> Unit) {
此外,为了使此魔术起作用,适配器必须执行以下3个步骤:
1. onBindViewHolder
将回调传递给持有者的bind
方法,该方法将为显示的元素调用selectionManager.selectPosition(position)
。 同样在这里,您很可能需要根据当前是否选中该项目来更改显示(通常仅是背景)-您可以为此调用selectionManager.isPositionSelected(position)
。
override fun onBindViewHolder(holder: TestHolder, position: Int) { val isItemSelected = selectionManager.isPositionSelected(position)
2. registerSelectionChangeListener
为了使适配器及时更新按下的元素,您必须订阅适当的操作。 并且不要忘记必须保存订阅方法返回的结果。
private val selectionDisposable = selectionManager.registerSelectionChangeListener { position, isSelected -> notifyItemChanged(position) }
我注意到在这种情况下, isSelected
参数的isSelected
并不重要,因为任何更改都会改变元素的外观。 但是,没有什么可以阻止您添加其他处理,对此值很重要。
3. selectionDisposable
在上一步中,我不仅仅是说必须保存该方法的结果-返回一个清除预订的对象,以避免泄漏。 完成工作后,必须咨询该对象。
fun destroy() { selectionDisposable.dispose() }
视图模型
对于魔术适配器就足够了,我们将传递给ViewModel
。 初始化SelectionManager
非常简单:
class TestViewModel: ViewModel() { val selectionManager: SelectionManager = SingleSelection() }
在这里,与适配器类似,您可以订阅更改(例如,当未选择任何项目时,使“删除”按钮不可用),但是您也可以单击摘要按钮(例如,“下载所选内容”)以获取所有已选择内容的列表。 。
fun onDownloadClick() { val selectedPositions: ArrayList<Int> = selectionManager.getSelectedPositions() ... }
这是我解决方案的缺点之一:在当前阶段,对象只能存储元素的位置。 也就是说,为了准确地获取选定的对象而不是它们的位置,将需要使用连接到适配器的数据源来附加逻辑(阿拉斯语,仅到目前为止)。 但希望您能解决。
此外,仅保留将适配器与视图模型连接。 这已经处于活动级别。
class TestActivity: AppCompatActivity() { private lateinit var adapter: TestAdapter private lateinit var viewModel: TestViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
柔韧性
对于某些人来说,这可能是可以理解的,但是我想特别指出的是,通过这种实现方式,可以很容易地控制适配器中的选择方法。 现在,适配器只能选择一个元素,但是如果TestViewModel
在TestViewModel
更改selectionManager
属性的初始化,则其余代码将“以新方式”工作,而无需进行任何更改。 也就是说,设置val selectionManager: SelectionManager = MultipleSelection()
,现在适配器允许您选择任意多个元素。
而且,如果您有整个应用程序的某种基本适配器类,则可以不必担心以相同的方式包含SelectionManager
。 确实,特别是对于根本不暗示选择元素的适配器,有一个NoneSelection
的实现-不管您做什么,它都不会拥有选定的元素,也永远不会调用任何侦听器。 不,他不会抛出异常-他只是忽略所有调用,但是适配器完全不需要知道这一点。
拦截器
在某些情况下,元素的选择更改会伴随其他操作(例如,加载详细信息),而无法成功完成更改操作会导致错误状态。 特别是对于这些情况,我添加了一种拦截机制。
要添加拦截器,您需要调用addSelectionInterceptor
方法(同样,您需要保存结果并在完成后访问它)。 callback: () -> Unit
示例中拦截器的参数之一callback: () -> Unit
直到被调用,更改才会应用。 也就是说,在没有网络的情况下,无法成功完成从服务器的详细信息的加载;因此,所使用的selectionManager
的状态将不会更改。 如果这正是您要争取的行为,则需要此方法。
val interceptionDisposable = selectionManager.addSelectionInterceptor { position: Int, isSelected: Boolean, callback: () -> Unit -> if(isSelected) { val selectedItem = ...
如有必要,您可以连接任意数量的拦截器。 在这种情况下,第一个拦截器的callback()
调用开始处理第二个拦截器。 并且只有最后一个中的callback()
会最终导致selectionManager
状态发生变化。
前景展望
- 使用
Disposable
清除订阅很有效,但不如LiveData
方便。 在线上的第一个改进是使用android.arch.lifecycle
的功能来进行更方便的工作。 很有可能,这将是一个单独的项目,以免在当前项目中增加平台依赖性。 - 就像我说的那样,获取选定对象的列表很不方便。 我还想尝试实现可以以相同方式与数据容器一起使用的对象。 同时,它可能是适配器的数据源。
参考文献
您可以在链接上找到源代码-GitHub
该项目也可以通过gradle实施ru.ircover.selectionmanager:core:1.0.0