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

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

结语思考:

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

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值