文章目录
一、动画
首先,克隆项目,代码如下:
git clone git@github.com:googlecodelabs/android-compose-codelabs.git && cd AnimationCodelab
1.1 颜色渐变动画
将 val backgroundColor = if (tabPage == TabPage.Home) Purple100 else Green300
替换为 val backgroundColor by animateColorAsState(if (tabPage == TabPage.Home) Purple100 else Green300)
,会让颜色有渐变效果,效果如下:
1.2 可见性渐变动画
原始代码为如下:
if (extended) {
Text(
text = stringResource(R.string.edit),
modifier = Modifier
.padding(start = 8.dp, top = 3.dp)
)
}
将其中的 if 替换为 AnimatedVisibility,代码如下:
AnimatedVisibility(extended) {
Text(
text = stringResource(R.string.edit),
modifier = Modifier
.padding(start = 8.dp, top = 3.dp)
)
}
预览后,效果如下:
通过下述代码,可以自定义动画,控制移动速度,代码如下:
AnimatedVisibility(
visible = shown,
enter = slideInVertically(
// Enters by sliding down from offset -fullHeight to 0.
initialOffsetY = { fullHeight -> -fullHeight },
animationSpec = tween(durationMillis = 150, easing = LinearOutSlowInEasing)
),
exit = slideOutVertically(
// Exits by sliding up from offset 0 to -fullHeight.
targetOffsetY = { fullHeight -> -fullHeight },
animationSpec = tween(durationMillis = 250, easing = FastOutLinearInEasing)
)
) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colors.secondary,
elevation = 4.dp
) {
Text(
text = stringResource(R.string.edit_message),
modifier = Modifier.padding(16.dp)
)
}
}
效果如下:
1.3 内容大小渐变动画
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.animateContentSize()
) {
// ... the title and the body
}
效果如下:
二、state
2.1 remember 和 mutableStateOf
@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
var count by remember { mutableStateOf(0) }
if (count > 0) {
Text(text = "你有 $count 杯水", modifier = modifier.padding(16.dp))
}
Button(onClick = { count++ }, enabled = count < 10, modifier = Modifier.padding(top = 8.dp)) {
Text("加一杯")
}
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
BasicStateCodelabTheme {
WaterCounter()
}
}
2.2 state 提升
@Composable
fun StatefulCounter(modifier: Modifier = Modifier) {
var appleCount by rememberSaveable { mutableStateOf(0) }
var bananaCount by rememberSaveable { mutableStateOf(0) }
Column() {
StatelessCounter(modifier = modifier, count = appleCount, onInc = { appleCount++ })
StatelessCounter(modifier = modifier, count = bananaCount, onInc = { bananaCount++ })
}
}
@Composable
private fun StatelessCounter(modifier: Modifier = Modifier, onInc: () -> Unit, count: Int) {
Column(modifier = modifier.padding(16.dp)) {
if (count > 0) {
Text(text = "你有 $count 杯水", modifier = modifier.padding(16.dp))
}
Button(onClick = onInc, enabled = count < 10, modifier = Modifier.padding(top = 8.dp)) {
Text("加一杯")
}
}
}
2.3 列表
@Composable
fun WellnessTaskItem(
taskName: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
onClose: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier, verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
text = taskName
)
Checkbox(
checked = checked,
onCheckedChange = onCheckedChange
)
IconButton(onClick = onClose) {
Icon(Icons.Filled.Close, contentDescription = "Close")
}
}
}
@Composable
fun WellnessTaskItem(taskName: String, modifier: Modifier = Modifier) {
var checkedState by rememberSaveable { mutableStateOf(false) }
WellnessTaskItem(
taskName = taskName,
checked = checkedState,
onCheckedChange = { newValue -> checkedState = newValue },
onClose = {}, // we will implement this later!
modifier = modifier,
)
}
@Composable
fun WellnessTaskList(modifier: Modifier = Modifier, list: List<WellnessTask> = remember { getWellnessTasks() }) {
LazyColumn(modifier = modifier) {
items(list) { task -> WellnessTaskItem(taskName = task.label) }
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
BasicStateCodelabTheme {
Column() {
StatefulCounter()
WellnessTaskList()
}
}
}
效果如下:
三、性能优化
3.1 release.minifyEnabled = true
build.gradle 的 release.minifyEnabled 需设置为 true,才有性能评估的意义,配置如下:
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
3.2 计算放在 remember 内,避免每帧都计算
我们应避免每次重组时都计算,例如下例代码中的 sort 计算:
应该改为下例,将计算放在 remember 函数内,代码如下:
3.3 List 的每项都有 key
如果不提供key,默认会以列表的下标做key,导致每当项在列表中移动时,就会重组,导致性能低下。注意 key 需保证唯一。
3.4 derivingStateOf{} 监听频繁变化,并仅挑选出我们所需状态的变化
如果想下滑屏幕时,显示 “回到顶部 Button",优化前可使用如下代码:
使用 derivaStateOf{}
优化后,其监听频繁变化的 listState,并仅挑选出我们需要的 listState.firstVisibleItemIdex > 0 的变化,当此变化发生时才重组,可以优化性能,可使用如下代码:
但是不要滥用,例如下例中,每当列表变化时,size 都会变化,使用前后变化的频率相同,而且还增加了 deriveStateOf{} 的开销,代码如下:
3.5 拖延
下例中,因为每帧 color 变量都会变化,导致每帧都会重组。
重组有3个阶段:composition、layout、draw,当 color 变量变化时,我们可以省略前两步骤(composition、layout),而只 draw。通过 延迟读取 可实现,代码如下:
3.6 尽量嵌套
尽量嵌套,例如下例中当 contact 变量变化时,只会调用 Text(),而不会调用 ContactCard() 和 MyCard(),因为他们并不读取 contact 变量。
3.7 不要再组合中,对读取到的值,再次写入
下例中,在 组合 未完成之前,balance 的值还在变化,代码如下:
其性能分析图如下:
其中 主线程 在界面没变化的情况下,非常忙(占用 CPU 很高),示例如下:
其实内部是这样执行的,示例如下:
我们不要再组合中,对读取到的值,再次写入,这违反了 Compose 的核心假设,会导致每帧都重组。
为了优化,我们在组合之前提前计算完所有结果,就只需要重组1次,开销很小,优化代码如下:
优化后,如预期一样,主线程刚开始很忙,后来就空闲了,示例如下:
更详细,可以观察到,只重组了1次,示例如下: