KMP(Kotlin Multiplatform)简单动画

一、背景

在界面开发过程中,会有很多根据条件显示隐藏View的过程,而直接显示隐藏显得比较生硬,采用KMP提供的动画组件可以很轻松的把交互过程变得更加流畅丝滑。

需求:在服务卡片中,点击可选服务,把卡片撑大,显示出更多内容。

使用渐变或者位移都不能很好实现,而KMP提供的滑动动画满足了需求。

二、实现过程

2.1 AnimatedVisibility用法

首先看AnimatedVisibility的源码,如下

复制代码
@Composable
public fun ColumnScope.AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandVertically(),
    exit: ExitTransition = fadeOut() + shrinkVertically(),
    label: String = "AnimatedVisibility",
    content: @Composable AnimatedVisibilityScope.() -> Unit
) {
    val transition = updateTransition(visible, label)
    AnimatedVisibilityImpl(transition, { it }, modifier, enter, exit, content = content)
}

其中,最主要关注3个参数,visible是控制内容显示隐藏,enter是显示的动画,exit是退出的动画。

默认enter的动画是fadeIn() + expandVertically(),也就是渐渐显示+扩大展开。

默认exit的动画是fadeOut() + shrinkVertically(),也就是渐渐隐藏+收缩关闭。

但尝试后,动画并不能满足诉求,于是对每一种动画做了尝试,最后发现slide动画结合使用满足了需求。

复制代码
AnimatedVisibility(
    showContent,
    enter = slideInVertically() + expandVertically(),
    exit = slideOutVertically() + shrinkVertically()
) {
    Column(modifier = Modifier.padding(bottom = 12.dp)) {
      【此处省略详细布局代码】
    }
}

把展开显示的布局写入AnimatedVisibility中接口。

2.2 事件处理

首先在Compose函数中声明显示隐藏变量

复制代码
var showContent by remember { mutableStateOf(false) }

在需要触发展开的布局上添加点击事件

复制代码
text = "可选服务(4)", style = TextStyle(
    fontSize = 12.sp,
    lineHeight = 16.sp,
    fontWeight = FontWeight(300),
    color = Color(0xFF8C8C8C),
), modifier = Modifier.clickable(
    interactionSource = remember { MutableInteractionSource() }, indication = null
) {
    showContent = !showContent
}.padding(vertical = 12.dp).fillMaxWidth()

如下在clickable事件中,改变showContent的值就可以控制AnimatedVisibility显示隐藏。

注解:这里clickable添加了interactionSource是为了去除点击的水波纹效果。

2.3 完整代码

复制代码
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kmpcappdidi.composeapp.generated.resources.Res
import kmpcappdidi.composeapp.generated.resources.ic_icon_help
import kmpcappdidi.composeapp.generated.resources.ic_icon_number_jia
import kmpcappdidi.composeapp.generated.resources.ic_icon_number_jian
import kmpcappdidi.composeapp.generated.resources.ic_icon_server_old
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview

@Composable
@Preview
fun App() {
    MaterialTheme {
        Column(
            modifier = Modifier.safeContentPadding().fillMaxSize()
                .background(color = Color(0xFFF5F5F5)),
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            ServerCardOld()
        }
    }
}

@Composable
fun ServerCardOld() {
    var showContent by remember { mutableStateOf(false) }
    Column(
        modifier = Modifier.padding(16.dp).background(
            color = Color(0xFFFFFFFF), shape = RoundedCornerShape(size = 4.dp)
        ).padding(horizontal = 16.dp)
    ) {
        Row(modifier = Modifier.padding(top = 12.dp)) {
            Image(
                painter = painterResource(Res.drawable.ic_icon_server_old),
                contentDescription = "image description",
                contentScale = ContentScale.None
            )
            Column(modifier = Modifier.padding(start = 12.dp)) {
                Text(
                    text = "有老人", style = TextStyle(
                        fontSize = 13.sp,
                        lineHeight = 16.sp,
                        fontWeight = FontWeight(500),
                        color = Color(0xFF262626),
                    )
                )

                Text(
                    text = "机场到达口中文举牌接您", style = TextStyle(
                        fontSize = 12.sp,
                        lineHeight = 16.sp,
                        fontWeight = FontWeight(300),
                        color = Color(0xFF8C8C8C),
                    ), modifier = Modifier.padding(top = 8.dp)
                )

                Text(
                    text = "可提供基础的讲解服务", style = TextStyle(
                        fontSize = 10.sp,
                        lineHeight = 12.sp,
                        fontWeight = FontWeight(300),
                        color = Color(0xFF595959),
                    ), modifier = Modifier.padding(top = 6.dp)
                )
            }

            Spacer(modifier = Modifier.weight(1f))

            Text(
                text = "免费", style = TextStyle(
                    fontSize = 11.sp,
                    lineHeight = 13.sp,
                    fontWeight = FontWeight(300),
                    color = Color(0xFF8C8C8C),
                )
            )
        }
        HorizontalDivider(
            modifier = Modifier.padding(top = 12.dp).height(0.5.dp).background(Color(0xFFF0F0F0))
        )

        Text(
            text = "可选服务(4)", style = TextStyle(
                fontSize = 12.sp,
                lineHeight = 16.sp,
                fontWeight = FontWeight(300),
                color = Color(0xFF8C8C8C),
            ), modifier = Modifier.clickable(
                interactionSource = remember { MutableInteractionSource() }, indication = null
            ) {
                showContent = !showContent
            }.padding(vertical = 12.dp).fillMaxWidth()
        )

        AnimatedVisibility(
            showContent,
            enter = slideInVertically() + expandVertically(),
            exit = slideOutVertically() + shrinkVertically()
        ) {
            Column(modifier = Modifier.padding(bottom = 12.dp)) {
                Row(
                    horizontalArrangement = Arrangement.Start,
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier.fillMaxWidth().background(
                        color = Color(0xFFFAFAFA), shape = RoundedCornerShape(size = 4.dp)
                    ).padding(start = 8.dp, top = 8.dp, end = 8.dp, bottom = 8.dp)
                ) {
                    Text(
                        text = "随车轮椅", style = TextStyle(
                            fontSize = 12.sp,
                            lineHeight = 16.sp,
                            fontWeight = FontWeight(400),
                            color = Color(0xFF000000),
                        )
                    )

                    Image(
                        painter = painterResource(Res.drawable.ic_icon_help),
                        contentDescription = "image description",
                        contentScale = ContentScale.None,
                        modifier = Modifier.padding(start = 4.dp)
                    )

                    Text(
                        text = "由可提供的向导报价", style = TextStyle(
                            fontSize = 9.sp,
                            lineHeight = 11.sp,
                            fontWeight = FontWeight(300),
                            color = Color(0xFF8C8C8C),
                        ), modifier = Modifier.padding(start = 4.dp)
                    )

                    Spacer(modifier = Modifier.weight(1f))

                    HNumberInput()
                }

                Row(
                    horizontalArrangement = Arrangement.Start,
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier.padding(top = 6.dp).fillMaxWidth().background(
                        color = Color(0xFFFAFAFA), shape = RoundedCornerShape(size = 4.dp)
                    ).padding(start = 8.dp, top = 8.dp, end = 8.dp, bottom = 8.dp)
                ) {
                    Text(
                        text = "帮带温水", style = TextStyle(
                            fontSize = 12.sp,
                            lineHeight = 16.sp,
                            fontWeight = FontWeight(400),
                            color = Color(0xFF000000),
                        )
                    )

                    Image(
                        painter = painterResource(Res.drawable.ic_icon_help),
                        contentDescription = "image description",
                        contentScale = ContentScale.None,
                        modifier = Modifier.padding(start = 4.dp)
                    )

                    Spacer(modifier = Modifier.weight(1f))

                    Checkbox(checked = false, onCheckedChange = {

                    }, modifier = Modifier.padding(end = 8.dp).height(14.dp).width(14.dp))
                }

                Row(
                    horizontalArrangement = Arrangement.Start,
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier.padding(top = 6.dp).fillMaxWidth().background(
                        color = Color(0xFFFAFAFA), shape = RoundedCornerShape(size = 4.dp)
                    ).padding(start = 8.dp, top = 8.dp, end = 8.dp, bottom = 8.dp)
                ) {
                    Text(
                        text = "下车陪同老人游览", style = TextStyle(
                            fontSize = 12.sp,
                            lineHeight = 16.sp,
                            fontWeight = FontWeight(400),
                            color = Color(0xFF000000),
                        )
                    )

                    Image(
                        painter = painterResource(Res.drawable.ic_icon_help),
                        contentDescription = "image description",
                        contentScale = ContentScale.None,
                        modifier = Modifier.padding(start = 4.dp)
                    )

                    Spacer(modifier = Modifier.weight(1f))

                    Checkbox(checked = false, onCheckedChange = {

                    }, modifier = Modifier.padding(end = 8.dp).height(14.dp).width(14.dp))
                }
            }

        }
    }
}

@Composable
fun HNumberInput() {
    val num = remember { mutableStateOf(0) }
    Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.height(20.dp)) {
        IconButton(onClick = {
            num.value--
        }) {
            Image(
                painter = painterResource(Res.drawable.ic_icon_number_jian),
                contentDescription = "image description",
                contentScale = ContentScale.None
            )
        }

        Text(
            "${num.value}", style = TextStyle(
                fontSize = 14.sp,
                lineHeight = 16.sp,
                fontWeight = FontWeight(500),
                color = Color(0xFF262626),
                textAlign = TextAlign.Center,
            )
        )

        IconButton(onClick = {
            num.value++
        }) {
            Image(
                painter = painterResource(Res.drawable.ic_icon_number_jia),
                contentDescription = "image description",
                contentScale = ContentScale.None
            )
        }
    }
}
相关推荐
Chester_19995 小时前
CSP202203C.计算资源调度器
开发语言·数据结构·c++·蓝桥杯
EXI-小洲6 小时前
Java 操作 Word:字符串替换、图片插入、动态生成表格与API接口下载
java·开发语言·spring boot·word
会周易的程序员7 小时前
给 PLC 写一个字节码虚拟机:STVM 虚拟机架构设计
开发语言·c++·虚拟机·软plc·iec61131·stvm
机器视觉知识推荐、就业指导7 小时前
为什么老说“纯 Qt 没啥就业市场”?
开发语言·qt
telepan7 小时前
Qt 开发避坑与性能指南:如何优雅、安全地定义全局常量字符串?
开发语言·qt
m0_695251497 小时前
Qt MaintenanceTool 使用国内镜像加速下载(超详细教程)
开发语言·qt
大衛說8 小时前
《Python 从入门到精通》系列总览与学习路线
开发语言·python·学习
_祝你今天愉快9 小时前
从驱动来分析服务的添加过程
android
可乐鸡翅yeah_10 小时前
hls.js 内存泄漏实战排查,直播 M3U8 长时间播放异常定位方案
开发语言·javascript·python·django·ecmascript·m3u8·m3u8在线
2601_9622035110 小时前
Java进阶07集合(续)
java·开发语言