You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 14:24:53