Jetpack Compose MotionLayout实战

3,000 阅读3分钟

「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战

MotionLayout

MotionLayout是一个非常新的类,它来自ConstraintLayout 2.0库中,主要目的是为了帮助Android开发人员在应用中降低使用手势和组件动画的难度。 MotionLayout可以在给定两组约束和一个进度开始到结束的情况下,插入其子布局的布局

MotionLayout小基础Jetpack Compose 用MotionLayout打造动画开关 本篇文章用MotionLayout实现文章列表到文章详细的过度动画

MotionLayout实战

首先导入依赖

//约束布局
implementation("androidx.constraintlayout:constraintlayout-compose:2.1.0")
复制代码

写这篇文章时时2.1.0

ConstraintSet

用于布局ConstraintLayout子项的约束的不变描述 分为开始和结束 使用MotionLayout时要传入开始和结束的ConstraintSet

item

item背景开始的ConstraintSet

宽度拉满,高度60吧,布局接着放在底部

background: { 
width: "spread",
height: 60,
start: ['parent', 'start', 16],
bottom: ['parent', 'bottom', 16],
end: ['parent', 'end', 16]
},

35f6bb6d2ca2fdcb0fb7b258a8f2639.jpg

item背景结束的ConstraintSet

background: { 
width: "spread",
height: 100,
start: ['parent', 'start', 0],
end: ['parent', 'end', 0],
top: ['parent', 'top', 0]
},

图片的区域

加个放图片的区域在左侧

图片开始的ConstraintSet

v1: { 
width: 100,
height: 60,
start: ['parent', 'start', 16],
bottom: ['parent', 'bottom', 16]
},

700f57236eefebb112aa6af5ab85849.jpg

图片结束的ConstraintSet

结束之后过渡成标题的背景,所以把它放上面
高度放大点100吧

v1: { 
width: "spread",
height: 100,
start: ['parent', 'start', 0],
end: ['parent', 'end', 0],
top: ['parent', 'top', 0]
},

标题

接着实现标题和副标题

文本开始的ConstraintSet

title: { 
width: "spread",
start: ['v1', 'end', 8],
top: ['v1', 'top', 8],
end: ['parent', 'end', 8],
custom: {
  textSize: 14
}
},
description: { 
start: ['v1', 'end', 8],
top: ['title', 'bottom', 0],
custom: {
  textSize: 12
}
},

6058e595e86cb8a13bb2bae8c61ef6b.jpg

文本结束的ConstraintSet

title: { 
width: "spread",
height: 28,
start: ['parent', 'start', 16],
top: ['background', 'tap', 16],
end: ['parent', 'end', 16],
custom: {
  textSize: 20
}
},
description: { 
width: "spread",
start: ['parent', 'start', 16],
top: ['v1', 'bottom', 8],
end: ['parent', 'end', 16],
custom: {
  textSize: 14
}
},

f7e6154090173bab2941cfef5320644.jpg 在加一个文章的背景

list: { 
width: "spread",
height: 0,
start: ['parent', 'start', 8],
end: ['parent', 'end', 8],
top: ['parent', 'bottom', 0]
},
list: { 
width: "spread",
height: 550,
start: ['parent', 'start', 16],
end: ['parent', 'end', 16],
top: ['description', 'bottom', 16]
},

c31c87085ba9a2a1ebd7ef6fa489244.jpg

最后加个关闭按钮

close: { 
end: ['parent', 'end', 24],
top: ['v1', 'top', 0],
bottom: ['v1', 'bottom', 0]
}
close: { 
start: ['parent', 'end', 8],
top: ['v1', 'top', 0],
bottom: ['v1', 'bottom', 0]
}

点击事件 定义开始状态

var animateToEnd by remember { mutableStateOf(false) }

动画的逻辑
持续时间1秒

val progress by animateFloatAsState(
    targetValue = if (animateToEnd) 1f else 0f,
    animationSpec = tween(1000)
)

绑定MotionLayouy

.layoutId({ConstraintSet的名称})

完整代码

@Composable
fun MotionLayout() {
    var animateToEnd by remember { mutableStateOf(false) }
    val progress by animateFloatAsState(
        targetValue = if (animateToEnd) 1f else 0f,
        animationSpec = tween(1000)
    )

    Column(Modifier.background(Color.White)) {
        MotionLayout(
            ConstraintSet(
                """ {
                    background: { 
                width: "spread",
                height: 60,
                start: ['parent', 'start', 16],
                bottom: ['parent', 'bottom', 16],
                end: ['parent', 'end', 16]
                },
                v1: { 
                width: 100,
                height: 60,
                start: ['parent', 'start', 16],
                bottom: ['parent', 'bottom', 16]
                },
                title: { 
                width: "spread",
                start: ['v1', 'end', 8],
                top: ['v1', 'top', 8],
                end: ['parent', 'end', 8],
                custom: {
                  textSize: 14
                }
                },
                description: { 
                start: ['v1', 'end', 8],
                top: ['title', 'bottom', 0],
                custom: {
                  textSize: 12
                }
                },
                list: { 
                width: "spread",
                height: 0,
                start: ['parent', 'start', 8],
                end: ['parent', 'end', 8],
                top: ['parent', 'bottom', 0]
                },

                close: { 
                end: ['parent', 'end', 24],
                top: ['v1', 'top', 0],
                bottom: ['v1', 'bottom', 0]
                }
            } """
            ),
            ConstraintSet(
                """ {
                background: { 
                width: "spread",
                height: 100,
                start: ['parent', 'start', 0],
                end: ['parent', 'end', 0],
                top: ['parent', 'top', 0]
                },
                v1: { 
                width: "spread",
                height: 100,
                start: ['parent', 'start', 0],
                end: ['parent', 'end', 0],
                top: ['parent', 'top', 0]
                },
                title: { 
                width: "spread",
                height: 28,
                start: ['parent', 'start', 16],
                top: ['background', 'tap', 16],
                end: ['parent', 'end', 16],
                custom: {
                  textSize: 20
                }
                },
                description: { 
                width: "spread",
                start: ['parent', 'start', 16],
                top: ['v1', 'bottom', 8],
                end: ['parent', 'end', 16],
                custom: {
                  textSize: 14
                }
                },
                list: { 
                width: "spread",
                height: 550,
                start: ['parent', 'start', 16],
                end: ['parent', 'end', 16],
                top: ['description', 'bottom', 16]
                },
                close: { 
                start: ['parent', 'end', 8],
                top: ['v1', 'top', 0],
                bottom: ['v1', 'bottom', 0]
                }
            } """
            ),
            progress = progress,
            modifier = Modifier
                .fillMaxSize()
                .background(Color.White)
        ) {
            Box(
                modifier = Modifier
                    .layoutId("background", "box")
                    .background(Color.Cyan)
                    .clickable(onClick = { animateToEnd = !animateToEnd })
            )
            Button(
                onClick = { animateToEnd = !animateToEnd },
                modifier = Modifier
                    .layoutId("v1", "box")
                    .background(Color.Blue),
                shape = RoundedCornerShape(0f)
            ) {}

            Text(
                text = "MotionLayout",
                modifier = Modifier.layoutId("title"),
                color = Color.Black,
                fontSize = motionProperties("title").value.fontSize("textSize")
            )
            Text(
                text = "jetpack Compose ",
                modifier = Modifier.layoutId("description"),
                color = Color.Black,
                fontSize = motionProperties("description").value.fontSize("textSize")
            )
            Box(
                modifier = Modifier
                    .layoutId("list", "box")
                    .background(Color.Gray)
            )

            Icon(
                Icons.Filled.Close,
                contentDescription = null,
                tint = Color.Black,
                modifier = Modifier.layoutId("close")
            )

        }
    }

效果图

ezgif.com-gif-maker (10).gif

部分代码来源Jetpack Compose Playground