Kotlin进阶之——拓展对集合的妙用

Kotlin进阶之——拓展对集合的妙用

码农世界 2024-05-23 前端 85 次浏览 0个评论

初识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(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 }
}

结语思考:

遍历不会影响性能吗?对象不会浪费内存吗?

转载请注明来自码农世界,本文标题:《Kotlin进阶之——拓展对集合的妙用》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,85人围观)参与讨论

还没有评论,来说两句吧...

Top