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


    初识kotlin的拓展,就大开眼界,原来调用工具类可以这么爽:

    1. 1.dp//转dp拓展
    2. "aa".toast()//吐司
    3. R.color.c_000.toColorInt//color资源转color值
    4. this.postMainDelayedLifecycle(100) {}//发送handler自动移除

    但对集合的拓展,我们似乎没有找到一个合适的功能,因为内置拓展几乎都满足了所有的场景。

    还记得我们经常写的列表单选或多选功能吗?这个似乎很简单,因为经常这样写:

    1. // 单选
    2. private var selectedPosition = -1
    3. fun onBindViewHolder(position: Int) {
    4. if (position == selectedPosition) {
    5. // ...
    6. } else {
    7. // ...
    8. }
    9. }
    10. // 多选
    11. private var selectedDataSet = ArraySet()//index、id同理
    12. fun onBindViewHolder(position: Int) {
    13. if (selectedDataSet.contains(list[position])) {
    14. // ...
    15. } else {
    16. // ...
    17. }

    当然涉及到数据嵌套(多级选择)、传递(传到其他地方)、可信源(多个数据备份)问题等各种神奇的骚操作就不再贴了,基本上都深有体会了吧。

    上面的这些思考一下很快能发现它是属于重复的模板代码。再想一下,“选中、不选中”到底是属于谁的功能?

    属于adapter的?上面的结果已经给出答案了。

    所以这个功能并不适合adapter做,因为选不选中就是item的事呀。而如何让item变化那必然少不了我们的数据bean。所以,如果在bean里加一个属性“isSelected”,那代码似乎就很简单了。

    1. class TestBean {
    2. var isSelected: Boolean = false
    3. // val...其他属性
    4. }
    5. //adapter里
    6. fun onBindViewHolder(position: Int) {
    7. if (list[position].isSelected) {
    8. // ...
    9. } else {
    10. // ...
    11. }
    12. }
    13. //获取选择的item
    14. fun getSelectedIndex(): Int {
    15. return list.indexOfFirst { it.isSelected }
    16. }

    很明显遍历又是模板代码,我们需要工具类来辅助,工具类又得需要具体对象,再进化一下代码,使用接口来约束“isSelected”字段,再且配上kotlin拓展大法:

    1. interface ISelectedListBean {
    2. var isSelected: Boolean
    3. }
    4. class TestBean:ISelectedListBean {
    5. override var isSelected: Boolean = false
    6. }
    7. /**
    8. * 当前选择的position
    9. */
    10. var List?.selectedPosition: Int
    11. set(value) {
    12. this?.forEachIndexed { index, bean ->
    13. bean.isSelected = index == value
    14. }
    15. }
    16. @IntRange(from = -1L)
    17. get() = this?.indexOfFirst { it.isSelected } ?: -1

    感觉上代码多了,但实践出真理,模板代码已经被抽出去了,使用起来那必须贼爽:

    1. //bind
    2. fun onBindViewHolder(position: Int) {
    3. if (list[position].isSelected) {
    4. // ...
    5. } else {
    6. // ...
    7. }
    8. }
    9. //获取或修改选择
    10. val selectedPosition = list.selectedPosition
    11. list.selectedPosition = 1
    12. //……其他操作

    至此真的结束了吗?

    别忘了我们还是有一个模板代码“ISelectedListBean”及相关继承逻辑。

    这是肯定想到,既然接口需要实现,那第一想法当然是抽象类了,加个抽象类就更简单了:

    1. abstract class AbsSelectedListBean : ISelectedListBean {
    2. override var isSelected: Boolean = false
    3. }

     当然此时的你应该非常敏锐了,这种写法会经常遇到一个问题——多继承冲突。有幸我们保留了接口,遇见这种问题退化到接口方式似乎也能轻松解决。但真的没有更好的解决方案了吗?

    再回头想想,对于“isSelected”这个属性是我们为我们的数据Bean额外加上去的功能,首先我们强制入侵了数据类,并且加功能似乎和某种设计模式的思想很像——装饰器模式。

    没错,如果我把“isSelected”当做数据Bean的装饰品,那你会发现一个新大陆。

    你的数据Bean完全不需要任何处理,你的adapter也不需要加任何代码。也就是说你几乎什么都没做,就已经带了选择状态,你只需要处理选择逻辑,完全无需关系相关的状态:

    1. /**
    2. * 原始类的装饰器
    3. */
    4. class SelectedListBeanWrapper<T>(val data: T) : ISelectedListBean {
    5. override var isSelected = false
    6. }
    7. //此时聪明的你应该还有个拓展
    8. /**
    9. * 使用装饰器来实现选择功能,可避免修改数据源
    10. */
    11. fun <T> Collection<T>.wrapToSelectedListBean() = this.map { SelectedListBeanWrapper(it) }
    12. //adapter
    13. class TestAdapter: BaseListAdapter<SelectedListBeanWrapper<String>>() {
    14. fun onBindViewHolder(position: Int) {
    15. if (list[position].isSelected) {
    16. val data = list[position].data
    17. // ...
    18. } else {
    19. // ...
    20. }
    21. }
    22. }

    对于嵌套、传递、可信源问题,当然不存在的,我们这时候完全可以把数据本身传递过去,只有一个数据源,所有操作都可依此为准。

    当然我们还是最好区分一下单选还是多选,所以最终代码如下:

    1. /**
    2. * 可以方便使用下方list.selected...的拓展
    3. * 建议使用[wrapToSingleSelectedListBean]、[wrapToMultiSelectedListBean]
    4. */
    5. interface ISelectedListBean {
    6. var isSelected: Boolean
    7. /**
    8. * 取反
    9. */
    10. fun inverterSelected() {
    11. isSelected = !isSelected
    12. }
    13. }
    14. /**
    15. * 原始类的装饰器
    16. */
    17. class SingleSelectedListBeanWrapper<T>(val data: T) : ISelectedListBean {
    18. override var isSelected = false
    19. }
    20. class MultiSelectedListBeanWrapper<T>(val data: T) : ISelectedListBean {
    21. override var isSelected = false
    22. }
    23. /**
    24. * 使用装饰器来实现选择功能,可避免修改数据源
    25. * 单选
    26. */
    27. fun Collection.wrapToSingleSelectedListBean() = this.map { SingleSelectedListBeanWrapper(it) }
    28. /**
    29. * 多选
    30. */
    31. fun Collection.wrapToMultiSelectedListBean() = this.map { MultiSelectedListBeanWrapper(it) }
    32. /**
    33. * 当前选择的position
    34. */
    35. var List>?.selectedPosition: Int
    36. set(value) {
    37. this?.forEachIndexed { index, bean ->
    38. bean.isSelected = index == value
    39. }
    40. }
    41. @IntRange(from = -1L)
    42. get() = this?.indexOfFirst { it.isSelected } ?: -1
    43. /**
    44. * 根据bean选中
    45. */
    46. fun List>?.setSelected(bean: T) {
    47. this?.forEach {
    48. it.isSelected = it == bean
    49. }
    50. }
    51. /**
    52. * 获取选择的那条数据
    53. */
    54. fun List>?.getSelectedData() = this?.getOrNull(selectedPosition)
    55. /**
    56. * 全选/全不选
    57. */
    58. var List>?.isAllSelected
    59. get() = this.allTrue { it.isSelected }
    60. set(value) {
    61. this?.forEach {
    62. it.isSelected = value
    63. }
    64. }
    65. /**
    66. * 获取全部选中的数据
    67. */
    68. fun List>?.getAllSelectData() = this?.filter { it.isSelected } ?: emptyList()
    69. /**
    70. * 获取选择的数量
    71. */
    72. val List>?.selectCount get() = this?.count { it.isSelected } ?: 0
    73. /**
    74. * 重置为未选中状态
    75. */
    76. fun List?.reseatData() {
    77. this?.forEach {
    78. it.isSelected = false
    79. }
    80. }
    81. /**
    82. * 是否有选中
    83. * @return true至少有一个选中
    84. */
    85. fun List?.hasSelected() = this.oneTrue { it.isSelected }
    86. /**
    87. * 指定item取反
    88. */
    89. fun List?.inverterSelected(index: Int) {
    90. this?.getOrNull(index)?.inverterSelected()
    91. }
    92. /**
    93. * 删除未选中的数据
    94. */
    95. fun MutableList?.removeUnselectData() {
    96. this?.removeIfIterator { item -> !item.isSelected }
    97. }

    结语思考:

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

  • 相关阅读:
    网络刷卡器开发,刷新移动物联新生活
    Docker的常用命令||Docker是个流行的容器化平台,它允许你打包、分发和运行应用程序。
    【SpringBoot】自动装配原理
    基于ISO14229协议的单帧以及多帧Can发送代码
    nocos配置中心使用教程(NACOS 1.X版本)
    28线性空间02—— 坐标变换
    一级造价工程师(安装)- 计量笔记 - 第五章第三节工业管道工程
    在win10中下载桌面版的docker并在docker中搭建运行基于linux的容器
    【云原生 | Kubernetes 系列】----K8s持续集成与部署
    之江实验室: 如何基于 JuiceFS 为超异构算力集群构建存储层 ?
  • 原文地址:https://blog.csdn.net/weimingjue/article/details/136298790