【Android-JetpackCompose】12、动画、state、性能优化

一、动画

首先,克隆项目,代码如下:

git clone [email protected]: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次,示例如下:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/jiaoyangwm/article/details/127205758