Jetpack Compose 结合 MotionLayout 给你不一样的动画惊喜

262 阅读3分钟

一、先看效果

二、代码实现

确定需要实现动画的开始和结束点

  1. 布局展开和闭合的状态
  2. 布局的宽度变化
  3. 布局的高度变化
  4. 布局的圆角变化
  5. 背景色的变化
  6. 文字颜色的变化

代码实现

底部的导航这里就不实现了,主要实现中间部分。

fun MotionLayoutSample() {
    //是否展开
    var expanded by remember { mutableStateOf(false) }
    //动画执行的进度
    val process by animateFloatAsState(
        targetValue = if (expanded) 1f else 0f,//展开就是 1f 否则 就是0f
        animationSpec = tween(1000)//动画执行 1秒
    )

    //宽度 初始宽度196.dp
    var withState by remember { mutableStateOf(196.dp) }
    //高度 初始高度 48.dp
    var heightState by remember { mutableStateOf(48.dp) }
    //圆角
    var percentCornerState by remember { mutableStateOf(50) }

    //背景颜色
    var colorBackgroundState by remember {
        mutableStateOf(Color.Black)
    }

    //飞机目的地文字颜色
    var colorTextState by remember {
        mutableStateOf(Color.White)
    }
    //飞机目的地文字大小
    var fontSizeState by remember {
        mutableStateOf(18.sp)
    }

    Scaffold {
        Box(
            modifier = Modifier
                .padding(it)
                .fillMaxSize(), contentAlignment = Alignment.Center
        ) {
            Box(modifier = Modifier.padding(16.dp), Alignment.TopCenter) {
                MotionLayout(
                    motionScene = getMotionScene() ,
                    progress = process,
                    modifier = Modifier
                        .width(withState)
                        .height(heightState)
                        .clip(RoundedCornerShape(percentCornerState))
                        .clickable {
                            //打开关闭控制
                            expanded = !expanded
                        }
                ) {
                    //读取MotionScene里面设置的大小
                    val colorBackground = motionProperties(id = "surface").value.color("color")
                    //读取高度
                    val newHeight = motionProperties(id = "surface").value.float("height")
                    //读取宽度
                    val newWidth = motionProperties(id = "surface").value.float("width")
                    //圆角
                    val percentCorner = motionProperties(id = "surface").value.int("corner")

                    //设置外面的布局大小
                    newHeight.run { heightState = this.dp }
                    newWidth.run { withState = this.dp }
                    percentCorner.run { percentCornerState = this }
                    colorBackground.run { colorBackgroundState = this }


                    //文字大小读取
                    val fontSize = motionProperties(id = "nameLabel").value.float("size")
                    fontSize.let { fontSizeState = TextUnit(fontSize, TextUnitType.Sp) }
                    //文字颜色
                    val newTextColor = motionProperties(id = "nameLabel").value.color("color")
                    newTextColor.run { colorTextState = this }

                    //内容显示背景
                    Box(
                        modifier = Modifier
                            .fillMaxSize()
                            .clip(RoundedCornerShape(percentCorner))
                            .background(colorBackground)
                            .layoutId("surface")
                    )
                    //图标 1
                    Icon(
                        painter = painterResource(id = R.drawable.ic_icbaselineflightland),
                        contentDescription = null,
                        tint = Color.Green,
                        modifier = Modifier
                            .size(28.dp)
                            .layoutId("icCall")
                    )

                    //图标2
                    Icon(
                        painter = painterResource(id = R.drawable.ic_icbaselineflight),
                        contentDescription = null,
                        tint = Color.Yellow,
                        modifier = Modifier
                            .width(68.dp)
                            .rotate(90f)//旋转 90度
                            .layoutId("arrow")
                    )
                    //飞机号
                    Text(
                        text = "FL228",
                        color = Color.White,
                        fontSize = 18.sp,
                        modifier = Modifier.layoutId("code")
                    )

                    //登机时间
                    Text(
                        text = "in 48m",
                        color = Color.White,
                        fontSize = 14.sp,
                        modifier = Modifier.layoutId("durationLabel")
                    )


                    //飞机目的地
                    Text(
                        text = "上海",
                        color = colorTextState,//变化的
                        fontSize = fontSizeState,//变化的
                        modifier = Modifier.layoutId("nameLabel")
                    )

                    //飞机出发地
                    Text(
                        text = "合肥",
                        color = Color.fromHex("#B6D8B0"),
                        fontSize = fontSizeState,
                        modifier = Modifier.layoutId("lastNameLabel")
                    )

                    //固定文字
                    Text(
                        text = "Landing",
                        color = Color.White,
                        fontSize = 14.sp,
                        modifier = Modifier.layoutId("descriptionLabel")
                    )

                }
            }
        }
    }
}

getMotionScene() 方法的实现

@SuppressLint("Range")
@OptIn(ExperimentalMotionApi::class)
@Composable
fun getMotionScene(): MotionScene {
    return MotionScene {
        //开始的布局
        val startMotion = constraintSet {
            val surface = createRefFor("surface")
            val icCall = createRefFor("icCall")
            val arrow = createRefFor("arrow")
            val nameLabel = createRefFor("nameLabel")
            val durationLabel = createRefFor("durationLabel")
            val descriptionLabel = createRefFor("descriptionLabel")
            val code = createRefFor("code")
            val lastNameLabel = createRefFor("lastNameLabel")
            //设置 背景 的宽高,角度,颜色
            constrain(surface) {
                //高
                customFloat("height", 48f)
                //宽
                customFloat("width", 196f)
                //角度
                customFloat("corner", 50f)
                //亚瑟
                customColor("color", Color.fromHex("#000000"))
            }
            //小图标
            constrain(icCall) {
                start.linkTo(parent.start, 16.dp)
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
            }
            //目的地
            constrain(nameLabel) {
                start.linkTo(icCall.end, 5.dp)
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
                customFloat("size", 18f)
                customColor("color", Color.fromHex("#ffffff"))
            }
            //时间
            constrain(durationLabel) {
                end.linkTo(parent.end, 16.dp)
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
            }

            //其他隐藏
            constrain(arrow) {
                start.linkTo(parent.start)
                end.linkTo(parent.end)
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
                scaleY = 0f
                scaleX = 1f
                alpha = 1f
            }

            constrain(descriptionLabel) {
                start.linkTo(parent.start)
                end.linkTo(parent.end)
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
                scaleY = 0f
                scaleX = 1f
                alpha = 1f
            }

            constrain(code) {
                start.linkTo(parent.start)
                end.linkTo(parent.end)
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
                scaleY = 0f
                scaleX = 1f
                alpha = 1f
            }

            constrain(lastNameLabel) {
                start.linkTo(parent.start)
                end.linkTo(parent.end)
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
                scaleY = 0f
                scaleX = 1f
                alpha = 1f
            }

        }
        //结束的布局
        val endMotion = constraintSet {
            val surface = createRefFor("surface")
            val icCall = createRefFor("icCall")
            val arrow = createRefFor("arrow")
            val nameLabel = createRefFor("nameLabel")
            val durationLabel = createRefFor("durationLabel")
            val descriptionLabel = createRefFor("descriptionLabel")
            val code = createRefFor("code")
            val lastNameLabel = createRefFor("lastNameLabel")
            //设置 背景 的宽高,角度,颜色
            constrain(surface) {
                customFloat("height", 188f)
                customFloat("width", 340f)
                customFloat("corner", 10f)
                customColor("color", Color.fromHex("#800000"))
            }

            //飞机的编号
            constrain(code) {
                start.linkTo(parent.start, 16.dp)
                top.linkTo(parent.top, 16.dp)
                alpha = 1f
            }
            //出发地
            constrain(lastNameLabel) {
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
                start.linkTo(parent.start, 16.dp)
            }

            //landing
            constrain(descriptionLabel) {
                bottom.linkTo(parent.bottom, 16.dp)
                start.linkTo(parent.start, 16.dp)
                alpha = 1f
            }
            //时间
            constrain(durationLabel) {
                start.linkTo(descriptionLabel.end)
                bottom.linkTo(parent.bottom, 16.dp)
            }
            //中间的大飞机
            constrain(arrow) {
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
                start.linkTo(parent.start)
                end.linkTo(parent.end)
                alpha = 1f
            }
            //隐藏小飞机
            constrain(icCall) {
                start.linkTo(parent.start, 16.dp)
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
                alpha = 0f
            }

            //目的地位置调整
            constrain(nameLabel) {
                end.linkTo(parent.end, 16.dp)
                top.linkTo(parent.top)
                bottom.linkTo(parent.bottom)
                customFloat("size", 48f)
                customColor("color", Color.fromHex("#50C7C1"))
            }
        }
        transition("default", startMotion, endMotion) {}
    }
}

颜色转换方法

private fun Color.Companion.fromHex(colorString: String): Color {
    return Color(android.graphics.Color.parseColor(colorString))
}