一、先看效果
二、代码实现
确定需要实现动画的开始和结束点
- 布局展开和闭合的状态
- 布局的宽度变化
- 布局的高度变化
- 布局的圆角变化
- 背景色的变化
- 文字颜色的变化
代码实现
底部的导航这里就不实现了,主要实现中间部分。
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))
}