在Compose的官方指导和示例代码中经常会看到这样的代码
var count by remember{mutableStateOf(0)}
或者
var count = remember{mutableStateOf(0)}
复制代码
首先需要注意几个问题
- 用by的话,count是Int类型【即mutableStateOf参数的类型】
- 用“=”, count是MutableState类型,使用时需要通过.value来取值
- 使用by的时候可能会报红,错误是
Type ‘TypeVariable(T)’ has no method ‘getValue(Nothing?, KProperty<*>)’ and thus it cannot serve as a delegate
解决方法很简单,导包即可
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
也可以直接:
import androidx.compose.runtime.*
复制代码
这个问题可能是Compose的一个bug,如果只是 by mutableStateOf(0),也会报错,但是按alt+enter 会提示需要导包,但是加了remember之后,按alt+enter是不提示导包的,非常奇怪。
4. 注意remember 后面是 {} , 不是()
进入正题,来看看mutableStateOf 和 remember 都是干嘛的
普通参数
class MainActivity : ComponentActivity() {
@ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
println("---- clicked onCreated setContent ")
Surface() {
var count = 0 // 无状态
Button(
onClick = { count++ }, modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.height(50.dp)
) {
Text(
text = "I have been clicked $count times",
modifier = Modifier.align(Alignment.CenterVertically)
)
SideEffect(effect = { println("---- text count = $count ") })
}
SideEffect(effect = { println("---- out count = $count ") })
}
}
}
}
复制代码
点击按钮,数字不变,控制台只有一次打印
2021-05-26 10:21:45.248 32194-32194/com.shakespace.compose I/System.out: ---- clicked onCreated setContent
2021-05-26 10:21:45.301 32194-32194/com.shakespace.compose I/System.out: ---- text count = 0
2021-05-26 10:21:45.301 32194-32194/com.shakespace.compose I/System.out: ---- out count = 0
复制代码
有状态参数
var count by mutableStateOf(0) // 改成mutableStateOf
复制代码
点击按钮,数字会不断增加,控制台输出
2021-05-26 10:26:36.949 16988-16988/com.shakespace.compose I/System.out: ---- clicked onCreated setContent
2021-05-26 10:26:37.002 16988-16988/com.shakespace.compose I/System.out: ---- text count = 0
2021-05-26 10:26:37.002 16988-16988/com.shakespace.compose I/System.out: ---- out count = 0
2021-05-26 10:26:40.511 16988-16988/com.shakespace.compose I/System.out: ---- text count = 1
2021-05-26 10:26:41.141 16988-16988/com.shakespace.compose I/System.out: ---- text count = 2
2021-05-26 10:26:41.792 16988-16988/com.shakespace.compose I/System.out: ---- text count = 3
2021-05-26 10:26:42.828 16988-16988/com.shakespace.compose I/System.out: ---- text count = 4
2021-05-26 10:26:43.596 16988-16988/com.shakespace.compose I/System.out: ---- text count = 5
复制代码
- 相比普通变量,使用mutableStateOf,使得变量有了被观察的能力,当值发生变化时就会通知使用这个变量的控件进行更新。
- 问题是这里没有用到remember,点击时数字还是会增加,是不是不需要remember呢?
重绘机制
把Button 的padding 改成和count 有关
setContent {
println("---- clicked onCreated setContent ")
Surface() {
var count by mutableStateOf(0)
Button(
onClick = { count++ }, modifier = Modifier
.padding(count.dp) // 把Button的padding 改成和count 有关
.fillMaxWidth()
.height(50.dp)
) {
Text(
text = "I have been clicked $count times",
modifier = Modifier.align(Alignment.CenterVertically)
)
SideEffect(effect = { println("---- text count = $count ") })
}
SideEffect(effect = { println("---- out count = $count ") })
}
}
复制代码
点击按钮,数字不会变化,控制台输出
2021-05-26 11:49:28.394 19077-19077/com.shakespace.compose I/System.out: ---- clicked onCreated setContent
2021-05-26 11:49:28.450 19077-19077/com.shakespace.compose I/System.out: ---- text count = 0
2021-05-26 11:49:28.450 19077-19077/com.shakespace.compose I/System.out: ---- out count = 0
2021-05-26 11:49:33.240 19077-19077/com.shakespace.compose I/System.out: ---- text count = 0
2021-05-26 11:49:33.240 19077-19077/com.shakespace.compose I/System.out: ---- out count = 0
2021-05-26 11:49:34.123 19077-19077/com.shakespace.compose I/System.out: ---- text count = 0
2021-05-26 11:49:34.123 19077-19077/com.shakespace.compose I/System.out: ---- out count = 0
2021-05-26 11:49:35.776 19077-19077/com.shakespace.compose I/System.out: ---- text count = 0
2021-05-26 11:49:35.776 19077-19077/com.shakespace.compose I/System.out: ---- out count = 0
复制代码
这时候数字不会变化,是因为Surface的直接子组件Button依赖于count这个state,那么count变化的时候,Surface接收的这个Composable函数就会重绘[方法重新调用],每次调用count就会是0,所以数字没有变化。
而前面Button不依赖于count的例子,外部不需要重绘(看日志 out 只打印了一次),count也不会重置,所以数字会增加。
remember
在这种情况下,如果还想记住变量值,就要用到remember
setContent {
println("---- clicked onCreated setContent ")
Surface() {
var count by remember{ mutableStateOf(0)} // 使用remember
Button(
onClick = { count++ }, modifier = Modifier
.padding(count.dp)
.fillMaxWidth()
.height(50.dp)
) {
Text(
text = "I have been clicked $count times",
modifier = Modifier.align(Alignment.CenterVertically)
)
SideEffect(effect = { println("---- text count = $count ") })
}
SideEffect(effect = { println("---- out count = $count ") })
}
}
复制代码
点击按钮,数字会增加,控制台输出:
2021-05-26 11:56:28.675 5449-5449/com.shakespace.compose I/System.out: ---- clicked onCreated setContent
2021-05-26 11:56:28.728 5449-5449/com.shakespace.compose I/System.out: ---- text count = 0
2021-05-26 11:56:28.729 5449-5449/com.shakespace.compose I/System.out: ---- out count = 0
2021-05-26 11:56:36.311 5449-5449/com.shakespace.compose I/System.out: ---- text count = 1
2021-05-26 11:56:36.311 5449-5449/com.shakespace.compose I/System.out: ---- out count = 1
2021-05-26 11:56:36.840 5449-5449/com.shakespace.compose I/System.out: ---- text count = 2
2021-05-26 11:56:36.840 5449-5449/com.shakespace.compose I/System.out: ---- out count = 2
2021-05-26 11:56:37.206 5449-5449/com.shakespace.compose I/System.out: ---- text count = 3
2021-05-26 11:56:37.206 5449-5449/com.shakespace.compose I/System.out: ---- out count = 3
复制代码
外部还是会重绘,但是每次值都会内存缓存中读取,这就是remember的作用,当当前Composable重绘的时候,可以暂存变量值。
如果把变量放在外面?
setContent {
println("---- clicked onCreated setContent ")
var count by mutableStateOf(0) // 移到外面,不适用remember
Surface() {
Button(
onClick = { count++ }, modifier = Modifier
.padding(count.dp)
.fillMaxWidth()
.height(50.dp)
) {
Text(
text = "I have been clicked $count times",
modifier = Modifier.align(Alignment.CenterVertically)
)
SideEffect(effect = { println("---- text count = $count ") })
}
SideEffect(effect = { println("---- out count = $count ") })
}
}
复制代码
数字会增加,控制台输出
2021-05-26 12:03:32.605 9375-9375/com.shakespace.compose I/System.out: ---- clicked onCreated setContent
2021-05-26 12:03:32.658 9375-9375/com.shakespace.compose I/System.out: ---- text count = 0
2021-05-26 12:03:32.658 9375-9375/com.shakespace.compose I/System.out: ---- out count = 0
2021-05-26 12:03:35.100 9375-9375/com.shakespace.compose I/System.out: ---- text count = 1
2021-05-26 12:03:35.100 9375-9375/com.shakespace.compose I/System.out: ---- out count = 1
2021-05-26 12:03:38.646 9375-9375/com.shakespace.compose I/System.out: ---- text count = 2
2021-05-26 12:03:38.646 9375-9375/com.shakespace.compose I/System.out: ---- out count = 2
2021-05-26 12:03:39.642 9375-9375/com.shakespace.compose I/System.out: ---- text count = 3
2021-05-26 12:03:39.642 9375-9375/com.shakespace.compose I/System.out: ---- out count = 3
复制代码
这和重绘那个例子是类似的,setContent的参数也是个Composable,Surface作为直接子类,并没有依赖于count,所以本身不会重绘,count也不会被重置。
但是
如果我们把上面的Surface换成Column之类的Composable,其他都不变,结果就不一样了
2021-05-26 12:07:16.163 9780-9780/com.shakespace.compose I/System.out: ---- clicked onCreated setContent
2021-05-26 12:07:16.166 9780-9780/com.shakespace.compose I/System.out: ---- text count = 0
2021-05-26 12:07:16.166 9780-9780/com.shakespace.compose I/System.out: ---- out count = 0
2021-05-26 12:07:17.047 9780-9780/com.shakespace.compose I/System.out: ---- clicked onCreated setContent
2021-05-26 12:07:17.050 9780-9780/com.shakespace.compose I/System.out: ---- text count = 0
2021-05-26 12:07:17.050 9780-9780/com.shakespace.compose I/System.out: ---- out count = 0
2021-05-26 12:07:17.883 9780-9780/com.shakespace.compose I/System.out: ---- clicked onCreated setContent
2021-05-26 12:07:17.885 9780-9780/com.shakespace.compose I/System.out: ---- text count = 0
2021-05-26 12:07:17.885 9780-9780/com.shakespace.compose I/System.out: ---- out count = 0
复制代码
数字不变,而且 “clicked onCreated setContent” 每次都打印,说明setContent里面的Composable每次都会重绘
目前所能知道的是,Column、Row、Box之类的组件,传入的Composable参数是给对应的Scope的扩展函数,而Surface接收的就是一个普通的Composable函数,一个可能不够准确的结论是:扩展函数中的直接组件重绘的话,相当于接收这个扩展函数的组件也要重绘。【即Button需要重绘,Column也会重绘】