Jetpack Compose中MotionLayout折叠/展开时无法动态调整高度
问题:Jetpack Compose MotionLayout折叠状态高度无法自适应
问题说明
使用MotionLayout实现列表项展开/折叠动画时,折叠状态下MotionLayout的黄色背景仍占用展开状态的高度,无法正确包裹内容。需要实现动画过程中高度随内容逐步增加、动态适配的效果。
当前Kotlin实现代码
@OptIn(ExperimentalMotionApi::class) @Preview(showBackground = true) @Composable fun Item2MotionLayout(progress: Float = 0f) { val context = LocalContext.current val sceneFile = R.raw.motion_live val motionSceneContent = remember(sceneFile) { context.resources .openRawResource(sceneFile) .readBytes() .decodeToString() } val motionScene = MotionScene(motionSceneContent) MotionLayout( motionScene = motionScene, progress = progress, modifier = Modifier .fillMaxWidth() .wrapContentHeight() .background(Color.Yellow) ) { Image( modifier = Modifier .size(48.dp) .clip(CircleShape) .layoutId("icon"), painter = painterResource(R.drawable.person), contentDescription = null, contentScale = ContentScale.Crop ) Text("Ali Asjad", modifier = Modifier.layoutId("title")) Text( "14-09-2024", fontSize = 11.sp, modifier = Modifier .padding(start = 10.dp) .layoutId("date") ) Text( stringResource(R.string.lorem), maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.layoutId("text") ) Image( modifier = Modifier .size(50.dp) .clip(RoundedCornerShape(0.dp)) .layoutId("picture"), painter = painterResource(R.drawable.person), contentDescription = null, contentScale = ContentScale.Crop ) Icon( imageVector = Icons.Filled.ArrowDropDown, modifier = Modifier .size(20.dp) .layoutId("arrowIcon"), contentDescription = null ) } }
原MotionScene配置JSON
{ ConstraintSets: { start: { icon: { start: ['parent', 'start'], top: ['parent', 'top'], bottom: ['parent', 'bottom'] }, title: { start: ['icon', 'end'], top: ['parent', 'top'] }, date: { start: ['title', 'end'], top: ['parent', 'top'] }, text: { start: ['icon', 'end'], end: ['picture', 'start'], top: ['title', 'bottom'], width: 'spread' }, picture: { end: ['arrowIcon', 'start'], top: ['parent', 'top'] }, arrowIcon: { end: ['parent', 'end'], top: ['parent', 'top'] } }, end: { icon: { start: ['parent', 'start'], top: ['parent', 'top'], bottom: ['text', 'bottom'] }, title: { start: ['icon', 'end'], top: ['parent', 'top'] }, date: { start: ['title', 'end'], top: ['parent', 'top'] }, text: { start: ['title', 'start'], end: ['arrowIcon', 'start'], top: ['title', 'bottom'], width: 'spread' }, picture: { start: ['icon', 'end'], top: ['text', 'bottom'], width: 150, height: 150 }, arrowIcon: { end: ['parent', 'end'], top: ['parent', 'top'] } } }, Transitions: { default: { from: 'start', to: 'end', KeyFrames: { KeyAttributes: [ { target: ['picture'] } ] } } } }
解决方案
1. 修正MotionScene的JSON语法错误
原JSON中所有键名(如ConstraintSets、start、end等)未加双引号,违反JSON语法规范,会导致MotionScene解析异常。需给所有键名添加双引号。
2. 明确约束集的父容器高度与子元素状态
- 在
start约束集中,设置父容器高度为wrap,同时将折叠状态下的picture元素尺寸设为0并隐藏,避免其占用空间:
"start": { "icon": { "start": ["parent", "start"], "top": ["parent", "top"], "bottom": ["parent", "bottom"], "margin": {"start": 16, "top": 16, "bottom": 16} }, "title": { "start": ["icon", "end"], "top": ["parent", "top"], "margin": {"start": 16, "top": 16} }, "date": { "start": ["title", "end"], "top": ["parent", "top"], "margin": {"start": 10, "top": 16} }, "text": { "start": ["icon", "end"], "end": ["parent", "end"], "top": ["title", "bottom"], "width": "spread", "margin": {"start": 16, "top": 8, "end": 16} }, "picture": { "width": 0, "height": 0, "visibility": "gone" }, "arrowIcon": { "end": ["parent", "end"], "top": ["parent", "top"], "margin": {"end": 16, "top": 16} }, "parent": { "height": "wrap" } }
- 在
end约束集中,同样设置父容器高度为wrap,确保展开后容器高度随内容自适应:
"end": { "icon": { "start": ["parent", "start"], "top": ["parent", "top"], "bottom": ["text", "bottom"], "margin": {"start": 16, "top": 16, "bottom": 16} }, "title": { "start": ["icon", "end"], "top": ["parent", "top"], "margin": {"start": 16, "top": 16} }, "date": { "start": ["title", "end"], "top": ["parent", "top"], "margin": {"start": 10, "top": 16} }, "text": { "start": ["title", "start"], "end": ["arrowIcon", "start"], "top": ["title", "bottom"], "width": "spread", "margin": {"start": 16, "top": 8, "end": 16} }, "picture": { "start": ["icon", "end"], "top": ["text", "bottom"], "width": 150, "height": 150, "margin": {"start": 16, "top": 16, "bottom": 16} }, "arrowIcon": { "end": ["parent", "end"], "top": ["parent", "top"], "margin": {"end": 16, "top": 16} }, "parent": { "height": "wrap" } }
3. 添加过渡动画的关键帧
在Transitions中给picture元素添加尺寸和可见性的过渡动画,实现折叠/展开时的平滑变化:
"Transitions": { "default": { "from": "start", "to": "end", "KeyFrames": { "KeyAttributes": [ { "target": ["picture"], "frames": [0, 100], "width": [0, 150], "height": [0, 150], "visibility": ["gone", "visible"] } ] } } }
4. 原理说明
原问题的核心原因是:
- MotionScene未明确设置父容器高度为
wrap,导致容器默认取两个约束集中的最大高度; - 折叠状态下
picture元素仍有尺寸,进一步撑开容器高度。
修正后,折叠状态下picture不可见且无尺寸,容器高度由其他可见元素的约束决定;动画过程中picture逐步显示并增大尺寸,容器高度随内容动态调整,实现平滑的展开/折叠效果。
内容的提问来源于stack exchange,提问作者Ali Asjad
相关产品推荐
相关产品推荐

