初识kotlin的拓展,就大开眼界,原来调用工具类可以这么爽:
- 1.dp//转dp拓展
- "aa".toast()//吐司
- R.color.c_000.toColorInt//color资源转color值
- this.postMainDelayedLifecycle(100) {}//发送handler自动移除
但对集合的拓展,我们似乎没有找到一个合适的功能,因为内置拓展几乎都满足了所有的场景。
还记得我们经常写的列表单选或多选功能吗?这个似乎很简单,因为经常这样写:
- // 单选
- private var selectedPosition = -1
- fun onBindViewHolder(position: Int) {
- if (position == selectedPosition) {
- // ...
- } else {
- // ...
- }
- }
-
-
- // 多选
- private var selectedDataSet = ArraySet
()//index、id同理 - fun onBindViewHolder(position: Int) {
- if (selectedDataSet.contains(list[position])) {
- // ...
- } else {
- // ...
- }
当然涉及到数据嵌套(多级选择)、传递(传到其他地方)、可信源(多个数据备份)问题等各种神奇的骚操作就不再贴了,基本上都深有体会了吧。
上面的这些思考一下很快能发现它是属于重复的模板代码。再想一下,“选中、不选中”到底是属于谁的功能?
属于adapter的?上面的结果已经给出答案了。
所以这个功能并不适合adapter做,因为选不选中就是item的事呀。而如何让item变化那必然少不了我们的数据bean。所以,如果在bean里加一个属性“isSelected”,那代码似乎就很简单了。
-
- class TestBean {
- var isSelected: Boolean = false
-
- // val...其他属性
- }
-
- //adapter里
- fun onBindViewHolder(position: Int) {
- if (list[position].isSelected) {
- // ...
- } else {
- // ...
- }
- }
- //获取选择的item
-
- fun getSelectedIndex(): Int {
- return list.indexOfFirst { it.isSelected }
- }
很明显遍历又是模板代码,我们需要工具类来辅助,工具类又得需要具体对象,再进化一下代码,使用接口来约束“isSelected”字段,再且配上kotlin拓展大法:
- interface ISelectedListBean {
- var isSelected: Boolean
- }
-
- class TestBean:ISelectedListBean {
- override var isSelected: Boolean = false
- }
-
- /**
- * 当前选择的position
- */
- var
List?.selectedPosition: Int - set(value) {
- this?.forEachIndexed { index, bean ->
- bean.isSelected = index == value
- }
- }
- @IntRange(from = -1L)
- get() = this?.indexOfFirst { it.isSelected } ?: -1
感觉上代码多了,但实践出真理,模板代码已经被抽出去了,使用起来那必须贼爽:
- //bind
- fun onBindViewHolder(position: Int) {
- if (list[position].isSelected) {
- // ...
- } else {
- // ...
- }
- }
-
- //获取或修改选择
- val selectedPosition = list.selectedPosition
- list.selectedPosition = 1
- //……其他操作
至此真的结束了吗?
别忘了我们还是有一个模板代码“ISelectedListBean”及相关继承逻辑。
这是肯定想到,既然接口需要实现,那第一想法当然是抽象类了,加个抽象类就更简单了:
- abstract class AbsSelectedListBean : ISelectedListBean {
- override var isSelected: Boolean = false
- }
当然此时的你应该非常敏锐了,这种写法会经常遇到一个问题——多继承冲突。有幸我们保留了接口,遇见这种问题退化到接口方式似乎也能轻松解决。但真的没有更好的解决方案了吗?
再回头想想,对于“isSelected”这个属性是我们为我们的数据Bean额外加上去的功能,首先我们强制入侵了数据类,并且加功能似乎和某种设计模式的思想很像——装饰器模式。
没错,如果我把“isSelected”当做数据Bean的装饰品,那你会发现一个新大陆。
你的数据Bean完全不需要任何处理,你的adapter也不需要加任何代码。也就是说你几乎什么都没做,就已经带了选择状态,你只需要处理选择逻辑,完全无需关系相关的状态:
-
- /**
- * 原始类的装饰器
- */
- class SelectedListBeanWrapper<T>(val data: T) : ISelectedListBean {
- override var isSelected = false
- }
-
-
- //此时聪明的你应该还有个拓展
- /**
- * 使用装饰器来实现选择功能,可避免修改数据源
- */
- fun <T> Collection<T>.wrapToSelectedListBean() = this.map { SelectedListBeanWrapper(it) }
-
-
-
- //adapter
- class TestAdapter: BaseListAdapter<SelectedListBeanWrapper<String>>() {
- fun onBindViewHolder(position: Int) {
- if (list[position].isSelected) {
- val data = list[position].data
- // ...
- } else {
- // ...
- }
- }
- }
-
对于嵌套、传递、可信源问题,当然不存在的,我们这时候完全可以把数据本身传递过去,只有一个数据源,所有操作都可依此为准。
当然我们还是最好区分一下单选还是多选,所以最终代码如下:
-
-
- /**
- * 可以方便使用下方list.selected...的拓展
- * 建议使用[wrapToSingleSelectedListBean]、[wrapToMultiSelectedListBean]
- */
- interface ISelectedListBean {
- var isSelected: Boolean
-
- /**
- * 取反
- */
- fun inverterSelected() {
- isSelected = !isSelected
- }
- }
-
- /**
- * 原始类的装饰器
- */
- class SingleSelectedListBeanWrapper<T>(val data: T) : ISelectedListBean {
- override var isSelected = false
- }
-
- class MultiSelectedListBeanWrapper<T>(val data: T) : ISelectedListBean {
- override var isSelected = false
- }
-
- /**
- * 使用装饰器来实现选择功能,可避免修改数据源
- * 单选
- */
- fun
Collection .wrapToSingleSelectedListBean() = this.map { SingleSelectedListBeanWrapper(it) } -
- /**
- * 多选
- */
- fun
Collection .wrapToMultiSelectedListBean() = this.map { MultiSelectedListBeanWrapper(it) } -
- /**
- * 当前选择的position
- */
- var
List>?.selectedPosition: Int - set(value) {
- this?.forEachIndexed { index, bean ->
- bean.isSelected = index == value
- }
- }
- @IntRange(from = -1L)
- get() = this?.indexOfFirst { it.isSelected } ?: -1
-
- /**
- * 根据bean选中
- */
- fun
List >?.setSelected(bean: T) { - this?.forEach {
- it.isSelected = it == bean
- }
- }
-
- /**
- * 获取选择的那条数据
- */
- fun
List >?.getSelectedData() = this?.getOrNull(selectedPosition) -
-
- /**
- * 全选/全不选
- */
- var
List>?.isAllSelected - get() = this.allTrue { it.isSelected }
- set(value) {
- this?.forEach {
- it.isSelected = value
- }
- }
-
- /**
- * 获取全部选中的数据
- */
- fun
List >?.getAllSelectData() = this?.filter { it.isSelected } ?: emptyList() -
- /**
- * 获取选择的数量
- */
- val
List>?.selectCount get() = this?.count { it.isSelected } ?: 0 -
- /**
- * 重置为未选中状态
- */
- fun
List ?.reseatData() { - this?.forEach {
- it.isSelected = false
- }
- }
-
- /**
- * 是否有选中
- * @return true至少有一个选中
- */
- fun
List ?.hasSelected() = this.oneTrue { it.isSelected } -
- /**
- * 指定item取反
- */
- fun
List ?.inverterSelected(index: Int) { - this?.getOrNull(index)?.inverterSelected()
- }
-
- /**
- * 删除未选中的数据
- */
- fun
MutableList ?.removeUnselectData() { - this?.removeIfIterator { item -> !item.isSelected }
- }
结语思考:
遍历不会影响性能吗?对象不会浪费内存吗?