首页 > 解决方案 > 在 Android Compose 中重构

问题描述

我正在使用新的 compose 库在 android中实现概述的TextField。但奇怪的是,输入数据没有在文本字段中更新。

于是搜了一下,发现android compose中有一个叫Recomposition的话题。我没有完全明白。
但是,我确实找到了解决方案:

@Composable
fun HelloContent(){
    var name:String by remember {mutableStateOf("")}
    OutlinedTextField(
        value = name,
        onValueChange = {name = it},
        label = {Text("Name")}
    )
}

我还阅读了jetpack compose 中的 State概念。但我无法完全得到它。
有人可以用简单的话解释吗?

标签: androidandroid-jetpack-composeandroid-jetpackandroid-jetpack-compose-text

解决方案


基本上,重组只是 Compose 中的一个事件,其中 Composable 被重新执行。在 Compose 所基于的声明式编码中,我们将 UI 编写为函数(或方法,更常见)。现在,重组基本上是通过重新执行可组合“函数”的主体来重新发出UI 的事件。这就是重组的核心。现在到它何时被触发。

好的,所以为了触发重组,我们需要一种特殊类型的变量。这种类型内置于 compose 中,专门设计用于让它知道何时重新组合。并且提到的类型是MutableState. 顾名思义,就是State,可以Mutate,即改变;各不相同。

所以,我们有一个类型的变量MutableState,接下来是什么?你猜怎么着,你没有类型变量,MutableState因为我没有教你如何创建一个!您将在 Compose 中使用的最常见分配是mutableStateOf. 这是一个预定义的方法,它实际上返回一个类型的MutableStateMutableState<T>T是这里的State类型,见下文

var a = mutableStateOf(999)

如上所示,999 是一个 int,因此,mutableStateOf这里将返回一个MutableState<Int>类型值。很容易。

现在,我们有一个MutableState<Int>值,但老实说,这有点难看。每次需要从 中获取值时MutableState<T>,都需要引用一个方便命名的属性.value

因此,要从上述 999 中取出var a,您需要调用a.value. 现在,这可以在一两个地方使用,但每次调用它似乎都是一团糟。这就是 Kotlin 属性委托的用武之地(我知道,我不需要将最后两个词大写)。我们使用by关键字从状态中检索值,并将其分配给我们的变量 - 这就是您应该关心的全部。

所以,var a by mutableStateOf(999)实际上会返回999type Int,而不是 type MutableState<Int>,但精彩的部分是 Compose 仍然会知道该变量a是 State-Holder。所以基本上mutableStateOf可以被认为是一个注册计数器,你只需要通过一次,就可以在State-Holders. 从何时开始,每次更改状态持有者之一的值时都会触发重组。这是一个粗略的想法,但让我们了解一下技术。现在,我们可以了解重组的“方式”。

要触发重组,您只需要确保两件事:

  1. Composable 应该读取一个变量,这也是一个状态持有者
  2. 状态持有者应该经历其当前价值的变化

佩里的例子让一切都变得更好:-

var a by mutableStateOf(999)

案例 1:一个 Composable 接收a作为参数值MyComposable(a),然后我运行a = 0,结果 1:Recomposition Triggered

案例二:变量a实际上是在 Composable 内部声明的,然后我运行var a = 12344 结果二:触发重组

案例 III:我重复案例 1 和 II,但使用不同的变量,如下所示:var b = 999
结果 III:未触发重组;原因:b不是国家持有者

太好了,我们现在掌握了基础知识。所以,这是本次讲座的最后阶段。

记住!!

你看,当我说在重组期间,整个 Composable 被重新执行,我的意思是整个 Composable 被重新执行,也就是说,每一行和每一个分配,没有例外。你看到这有什么问题了吗?让我示范

假设我想要一个TextComposable 显示一个数字,并在我单击它时增加该数字。

我可以实现这样简单的东西

@Composable
fun CountingText(){
 var n = mutableStateOf(0) //Starts at 0
 Text(
  value = n.toString(), //The Composable only accepts strings, while n is of Int type
  modifier = Modifier
             .clickable { n++ }
 )
}

好的,这是我们可能认为可行的实现。如果您不熟悉Modifiers,请暂时保留它,并相信我clickable,当您实际单击Text. 现在,让我们想象一下这将如何执行。

首先 Compose 会将变量注册n为状态持有者。然后它将呈现Text具有初始值0的Composable n

现在,Text实际上是clicked。里面的块clicakble将被执行,在这种情况下只是n++,它将更新 的值n。Compose 看到 的值为n更新,并遍历状态持有者列表。Compose 发现n确实是一个状态持有者,然后决定触发重组。现在,读取 的值的整个 Composablen将被重新组合。在这种情况下,该 Composable 是 CountingText因为它Text内部正在读取n(To display it) 的值。因此,CountingText将被“重新执行”。让我们来看看这里的重新执行。

Composable 中的第一行,

var n = mutableStateOf(0)

n变成了0。

下一行:-

Text(
  value = n.toString(), //Just displays 0 
  modifier = Modifier 
             .clickable { n++ } //Just tells it to increase n upon click
 )

所以你看,这里的问题是,在重新执行时,n它是完全从头开始创建的,就好像它以前从未存在过一样。它已从 Composable 的内存中删除。为了解决这个问题,我们需要 Composable to remember n. 这样,Compose 就知道这是一个状态持有者,并且拥有一个需要在重组时重新分配给它的值。所以,这里只是更新的第一行

var n by remember { mutableStateOf(0) }

现在,在第一次执行时,n将收到 0,因为它实际上是第一次n创建。感谢remembern现在可以访问Composable的内存,因此将存储在内存中以供将来使用。

因此,在重组期间,会发生这种情况 - 当执行程序 (???) 到达n分配的行时,

var n by remember { mutableStateOf(0) }

remember实际上充当了看门人,并且不允许执行者进入其中包含的块。相反,它将先前记住的值传递给它并要求它继续前进。因为当用户单击文本时,它已经将值n增加到 1,这被保留在内存中,所以现在它可以按预期工作。

这与您的 TextField 问题相同。该字段最初读取一个空值,并且每次用户键入一个字母时都会更新该值,触发重组并最终在屏幕上显示正确的值。

它可以变得足够简单吗?让我知道我花了半个小时打字。


推荐阅读