初识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 */ varList ?.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(val data: T) : ISelectedListBean { override var isSelected = false } //此时聪明的你应该还有个拓展 /** * 使用装饰器来实现选择功能,可避免修改数据源 */ fun Collection .wrapToSelectedListBean() = this.map { SelectedListBeanWrapper(it) } //adapter class TestAdapter: BaseListAdapter >() { 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(val data: T) : ISelectedListBean { override var isSelected = false } class MultiSelectedListBeanWrapper (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 } }
结语思考:
遍历不会影响性能吗?对象不会浪费内存吗?
还没有评论,来说两句吧...