Android Compose Column列表不自动刷新问题

作者:氦客 时间:2022-08-10 07:48:02 

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")
   }
}

效果如下

Android Compose Column列表不自动刷新问题

但是如果是使用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()
       )
   }
}

可以看到,点击了按钮后,列表完全没有刷新

Android Compose Column列表不自动刷新问题

这是为什么了 ?

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()
       )
   }
}

Android Compose Column列表不自动刷新问题

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()返回的是继承自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)

这里就是重点,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

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")
   }
}

来源:https://blog.csdn.net/EthanCo/article/details/127461168

标签:Android,Compose,Column,列表刷新
0
投稿

猜你喜欢

  • 初学C#所需明白的那些点

    2023-11-26 14:40:50
  • Android开发之SD卡文件操作分析

    2022-01-09 04:55:16
  • Android编程实现加载等待ProgressDialog的方法

    2022-09-11 21:12:11
  • C# 对Outlook2010进行二次开发的图文教程

    2022-02-03 00:34:34
  • Android 关闭多个Activity的实现方法

    2022-02-24 21:21:19
  • C# linq查询之动态OrderBy用法实例

    2023-11-04 04:22:56
  • Android获取设备CPU核数、时钟频率以及内存大小的方法

    2022-09-13 07:09:32
  • mybatisPlus返回Map类型的集合

    2022-01-31 13:37:07
  • Android编程实现等比例显示图片的方法

    2022-05-20 03:37:34
  • Spring boot热部署devtools过程解析

    2022-06-21 13:12:09
  • Android 仿QQ头像自定义截取功能

    2023-05-14 00:13:21
  • MyBatis入门学习教程-MyBatis快速入门

    2023-10-25 05:06:07
  • Android 将文件下载到指定目录的实现代码

    2021-06-11 04:44:41
  • Javaweb会话跟踪技术Cookie和Session的具体使用

    2022-06-24 21:16:33
  • 浅谈Java8 判空新写法

    2023-06-21 15:55:08
  • springboot+zookeeper实现分布式锁的示例代码

    2022-02-05 08:48:48
  • Java 网络编程socket编程等详解

    2023-09-09 16:43:12
  • JavaWeb动态导出Excel可弹出下载

    2021-11-21 11:12:12
  • 解析C#中[],List,Array,ArrayList的区别及应用

    2022-07-29 16:24:10
  • Android自定义View之继承TextView绘制背景

    2021-11-05 11:16:06
  • asp之家 软件编程 m.aspxhome.com