初识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<String>()//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 <T : ISelectedListBean> List<T>?.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 <T> Collection<T>.wrapToSingleSelectedListBean() = this.map { SingleSelectedListBeanWrapper(it) }
/**
* 多选
*/
fun <T> Collection<T>.wrapToMultiSelectedListBean() = this.map { MultiSelectedListBeanWrapper(it) }
/**
* 当前选择的position
*/
var <T> List<SingleSelectedListBeanWrapper<T>>?.selectedPosition: Int
set(value) {
this?.forEachIndexed { index, bean ->
bean.isSelected = index == value
}
}
@IntRange(from = -1L)
get() = this?.indexOfFirst { it.isSelected } ?: -1
/**
* 根据bean选中
*/
fun <T> List<SingleSelectedListBeanWrapper<T>>?.setSelected(bean: T) {
this?.forEach {
it.isSelected = it == bean
}
}
/**
* 获取选择的那条数据
*/
fun <T> List<SingleSelectedListBeanWrapper<T>>?.getSelectedData() = this?.getOrNull(selectedPosition)
/**
* 全选/全不选
*/
var <T> List<MultiSelectedListBeanWrapper<T>>?.isAllSelected
get() = this.allTrue { it.isSelected }
set(value) {
this?.forEach {
it.isSelected = value
}
}
/**
* 获取全部选中的数据
*/
fun <T > List<MultiSelectedListBeanWrapper<T>>?.getAllSelectData() = this?.filter { it.isSelected } ?: emptyList()
/**
* 获取选择的数量
*/
val <T> List<MultiSelectedListBeanWrapper<T>>?.selectCount get() = this?.count { it.isSelected } ?: 0
/**
* 重置为未选中状态
*/
fun <T : ISelectedListBean> List<T>?.reseatData() {
this?.forEach {
it.isSelected = false
}
}
/**
* 是否有选中
* @return true至少有一个选中
*/
fun <T : ISelectedListBean> List<T>?.hasSelected() = this.oneTrue { it.isSelected }
/**
* 指定item取反
*/
fun <T : ISelectedListBean> List<T>?.inverterSelected(index: Int) {
this?.getOrNull(index)?.inverterSelected()
}
/**
* 删除未选中的数据
*/
fun <T : ISelectedListBean> MutableList<T>?.removeUnselectData() {
this?.removeIfIterator { item -> !item.isSelected }
}
结语思考:
遍历不会影响性能吗?对象不会浪费内存吗?