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")
- }
- }
效果如下

但是如果是使用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()
- )
- }
- }
可以看到,点击了按钮后,列表完全没有刷新

这是为什么了 ?
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()
- )
- }
- }

3. 原因
解决方案很简单,但是这是为什么呢 ?
3.1 mutableStateOf为什么可以更新UI
我们以mutableStateOf()
这个为例
- var value by mutableStateOf(0)
首先,我们要明白,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
- }
查看mutableStateOf
源码,可以发现,mutableStateOf()
返回的是继承自MutableState
的SnapshotMutableState
对象,路径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)
这里就是重点,SnapshotMutableStateImpl
的value
属性重写了get()
和set()
方法
- 当
value
被读的时候,不光把值返回,还会记录一下在哪被读的 - 当
value
被写的时候,不止把这个值给改了,还会去查找在哪里被读过,然后通知这些被读过的地方,通知UI
进行刷新
4. 结论
因为我们操作String
、Int
等基础类型的时候,都是通过get
、set()
来获取、设置数据的,所以这操作会被SnapshotMutableStateImpl
记录下来,而List
、Map
这种集合,我们是通过add
、remove
来更新数据的,所以不会触发SnapshotMutableStateImpl
value
属性的set
。
4.1 解决方案一
使用mutableStateListOf
替代mutableStateOf
,mutableStateListOf
内部对add
、remove
方法也进行了重写
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
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")
- }
- }
到此这篇关于Android Compose Column列表不自动刷新问题的文章就介绍到这了,更多相关Android Compose Column内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!