• Android Compose Column列表 数据更新列表不刷新的问题


    1. 背景

    我们都知道,Compose可以使用mutableStateOf和UI进行绑定,改变值之后,就可以改变UI。

    var value by remember { mutableStateOf(0) }
    var imageVisible by remember { mutableStateOf(true) }
    Column {
        Text(text = "现在的值是:$value")
        Button(onClick = {
            value++ //修改值,自动改变UI
        }) {
            Text(text = "Add Value")
        }
        AnimatedVisibility(visible = imageVisible) {
            Image(
                painter = painterResource(id = R.mipmap.photot1),
                contentDescription = "",
                Modifier.width(260.dp)
            )
        }
        Button(onClick = {
            imageVisible = !imageVisible //修改值,自动显示/隐藏UI
        }) {
            Text(text = "Show/Hide")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    效果如下
    在这里插入图片描述

    但是如果是使用Column/Row/LazyColumn/LazyRow列表的时候,无论怎么更新数据,界面都不会刷新

    val list = ArrayList<String>()
    for (i in 0..10) {
        list.add(i.toString())
    }
    
    var stateList by remember { mutableStateOf(list) }
    
    Button(onClick = {
        stateList.add("添加的值:${Random.nextInt()}")
    }, modifier = Modifier.fillMaxWidth()) {
        Text(text = "添加值")
    }
    Button(onClick = {
        stateList.removeAt(stateList.size - 1)
    }, modifier = Modifier.fillMaxWidth()) {
        Text(text = "删除值")
    }
    
    LazyColumn {
        items(stateList.size) { index ->
            Text(
                text = "${stateList.get(index)}",
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .height(24.dp)
                    .fillMaxWidth()
            )
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    可以看到,点击了按钮后,列表完全没有刷新
    在这里插入图片描述
    这是为什么了 ?

    2. 解决方案

    当时很不解,为啥其他类型都是可以的,使用List就不行了呢 ?
    查阅了好久,终于找到了解决方案
    mutableStateOf改用mutableStateListOf就可以了

    var stateList = remember { mutableStateListOf<String>() }
    for (i in 0..10) {
        stateList.add(i.toString())
    }
    
    Button(onClick = {
        stateList.add("添加的值:${Random.nextInt()}")
    }, modifier = Modifier.fillMaxWidth()) {
        Text(text = "添加值")
    }
    Button(onClick = {
        stateList.removeAt(stateList.size - 1)
    }, modifier = Modifier.fillMaxWidth()) {
        Text(text = "删除值")
    }
    LazyColumn {
        items(stateList.size) { index ->
            Text(
                text = "${stateList.get(index)}",
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .height(24.dp)
                    .fillMaxWidth()
            )
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    3. 原因

    解决方案很简单,但是这是为什么呢 ?

    3.1 mutableStateOf为什么可以更新UI

    我们以mutableStateOf()这个为例

    var value by mutableStateOf(0)
    
    • 1

    首先,我们要明白,mutableStateOf()返回的是一个MutableState对象,MutableState中有一个var value: T属性

    interface MutableState<T> : State<T> {
        override var value: T
        operator fun component1(): T
        operator fun component2(): (T) -> Unit
    }
    
    interface State<out T> {
        val value: T
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    查看mutableStateOf源码,可以发现,mutableStateOf()返回的是继承自MutableStateSnapshotMutableState对象,路径mutableStateOf()-> createSnapshotMutableState() -> ParcelableSnapshotMutableState-> SnapshotMutableStateImpl,可以看到有这样一段代码

    override var value: T
        get() = next.readable(this).value
        set(value) = next.withCurrent {
            if (!policy.equivalent(it.value, value)) {
                next.overwritable(this, it) { this.value = value }
            }
        }
    
    private var next: StateStateRecord<T> = StateStateRecord(value)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里就是重点,SnapshotMutableStateImplvalue属性重写了get()set()方法

    • value被读的时候,不光把值返回,还会记录一下在哪被读的
    • value被写的时候,不止把这个值给改了,还会去查找在哪里被读过,然后通知这些被读过的地方,通知UI进行刷新

    4. 结论

    因为我们操作StringInt等基础类型的时候,都是通过getset()来获取、设置数据的,所以这操作会被SnapshotMutableStateImpl记录下来,而ListMap这种集合,我们是通过addremove来更新数据的,所以不会触发SnapshotMutableStateImpl value属性的set

    4.1 解决方案一

    使用mutableStateListOf替代mutableStateOfmutableStateListOf内部对addremove方法也进行了重写

    4.2 解决方案二

    新创建一个List,然后赋值给原来的list,这样就会触发set

    var stateList by remember { mutableStateOf(list) }
    
    val tempList = ArrayList<String>()
    for (value in stateList) {
        tempList.add(value)
    }
    tempList.add("添加的值:${Random.nextInt()}")
    stateList = tempList //赋值的时候会触发刷新UI
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5.自己实现一个mutableStateOf()

    我们也可以自己来实现一个mutableStateOf,伪代码如下

    class Test {
        interface State<out T> {
            val value: T
        }
    
        interface MutableState<T> : State<T> {
            override var value: T
            /*operator fun component1(): T
            operator fun component2(): (T) -> Unit*/
        }
    
        inline operator fun <T> State<T>.getValue(thisObj: Any?, property: KProperty<*>): T = value
    
        inline operator fun <T> MutableState<T>.setValue(
            thisObj: Any?,
            property: KProperty<*>,
            value: T
        ) {
            this.value = value
        }
    
        interface SnapshotMutableState<T> : MutableState<T> {
            val policy: SnapshotMutationPolicy<T>
        }
    
        interface SnapshotMutationPolicy<T> {
            fun equivalent(a: T, b: T): Boolean
    
            fun merge(previous: T, current: T, applied: T): T? = null
        }
    
        internal open class SnapshotMutableStateImpl<T>(
            val _value: T,
            override val policy: SnapshotMutationPolicy<T>
        ) : /*StateObject, */SnapshotMutableState<T> {
            private var next : T = 52 as T
    
            @Suppress("UNCHECKED_CAST")
            override var value: T
                get() = next
                /*get() {
                    Log.i(TAGs.TAG, "getValue:$field")
                    return "" as T
                }*/
                set(value) {
                    Log.i(TAGs.TAG, "setValue")
                    this.value = value
                }
    
            /*override fun component1(): T {
                //TODO("Not yet implemented")
            }
    
            override fun component2(): (T) -> Unit {
                //TODO("Not yet implemented")
            }*/
    
        }
    
        internal class ParcelableSnapshotMutableState<T>(
            value: T,
            policy: SnapshotMutationPolicy<T>
        ) : SnapshotMutableStateImpl<T>(value, policy)/*, Parcelable*/ {
    
        }
    
        fun <T> mutableStateOf(
            value: T,
            policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
        ): MutableState<T> = createSnapshotMutableState(value, policy)
    
        fun <T> structuralEqualityPolicy(): SnapshotMutationPolicy<T> =
            StructuralEqualityPolicy as SnapshotMutationPolicy<T>
    
        private object StructuralEqualityPolicy : SnapshotMutationPolicy<Any?> {
            override fun equivalent(a: Any?, b: Any?) = a == b
    
            override fun toString() = "StructuralEqualityPolicy"
        }
    
        fun <T> createSnapshotMutableState(
            value: T,
            policy: SnapshotMutationPolicy<T>
        ): SnapshotMutableState<T> = ParcelableSnapshotMutableState(value, policy)
    
        fun main() {
            var sizeUpdate by mutableStateOf(48)
            Log.i(TAGs.TAG, "sizeUpdate:$sizeUpdate")
            sizeUpdate = 64
            Log.i(TAGs.TAG, "sizeUpdate>>$sizeUpdate")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
  • 相关阅读:
    javascript学习之数据类型转换
    一位工作多年的测试人告诉你哪些抓包工具指的推荐~
    数据一致性离不开的checkpoint机制
    MySQL第六讲·where和having的异同?
    华为面试100题:java开发工程师(中)
    闭包的产生和问题
    传智书城源码+课程设计文档基于JSP+Servlet实现
    css:居中的几种布局方式
    使用ShowMeTheXAML来展示XAML代码
    FAA批准uAvionix C波段超视距飞行(BVLOS)豁免和Vantis网络
  • 原文地址:https://blog.csdn.net/EthanCo/article/details/127461168