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

Jetpack Compose中MotionLayout与LazyColumn滑动动画交互异常排查

解决MotionLayout与LazyColumn的滑动冲突问题

核心问题分析

你的问题根源有两个:

  1. MotionScene配置中onSwipe的锚点content在Compose代码中没有对应的layoutId组件,导致MotionLayout无法正确识别滑动触发区域;
  2. LazyColumn会优先拦截触摸滚动事件,抢占了MotionLayout的滑动动画触发权。

以下是具体的解决方案:


步骤1:修正MotionScene配置

将MotionScene中的锚点和约束集目标改为Compose中实际存在的front(即包含LazyColumn的组件),修改后的配置如下:

{
  ConstraintSets: {
    start: {
      hero: {
        width: "spread",
        top: ["parent", "top"],
        start: ["parent", "start"],
        end: ["parent", "end"],
        translationY: 0,
        alpha: 1,
        scaleX: 1,
        scaleY: 1,
      },
      front: {
        width: "spread",
        height: "spread",
        start: ["parent", "start"],
        end: ["parent", "end"],
        top: ["hero","bottom"],
        bottom: ["parent","bottom"],
      },
    },
    end: {
      hero: {
        width: "spread",
        top: ["parent", "top"],
        start: ["parent", "start"],
        end: ["parent", "end"],
        translationY: -50,
        alpha: 0,
        scaleX: 0.8,
        scaleY: 0.8,
      },
      front: {
        width: "spread",
        height: "spread",
        start: ["parent", "start"],
        end: ["parent", "end"],
        top: ["parent","top"],
        bottom: ["parent","bottom"],
      },
    },
  },
  Transitions: {
    default: {
      from: "start",
      to: "end",
      onSwipe: {
        anchor: "front",
        direction: "bottom",
        side: "top",
      },
    }
  }
}

步骤2:修改Compose代码,协调触摸事件

通过NestedScrollConnection让MotionLayout优先处理滑动动画,同时根据动画进度控制LazyColumn的滚动权限:

@OptIn(ExperimentalMotionApi::class)
@Composable
fun HomeView(
    navController: NavController,
    viewModel: AuthViewModel = hiltViewModel()
) {

    val context = LocalContext.current
    val motionScene = remember { 
        context.resources.openRawResource(R.raw.home_main_scene).readBytes().decodeToString() 
    }
    val tabIndex = remember { mutableStateOf(0) }
    val motionState = rememberMotionLayoutState()
    val systemUiController = rememberSystemUiController()

    SideEffect {
        systemUiController.setNavigationBarColor(
            color = LightColors.lightDebianColor,
            darkIcons = true
        )
        systemUiController.setStatusBarColor(
            color = PrimaryDebianColors.primary,
        )
    }

    // 创建嵌套滚动连接,让MotionLayout优先处理滑动事件
    val nestedScrollConnection = remember {
        object : NestedScrollConnection {
            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                // 动画未完成时,拦截向上滑动事件(触发MotionLayout动画的方向)
                if (motionState.progress < 1f && available.y < 0) {
                    return Offset(0f, available.y)
                }
                return Offset.Zero
            }
        }
    }

    MotionLayout(
        motionScene = MotionScene(content = motionScene),
        motionLayoutState = motionState,
        modifier = Modifier
            .fillMaxSize()
            .nestedScroll(nestedScrollConnection),
    ) {
        Box(
            modifier = Modifier.layoutId("hero"),
            contentAlignment = Alignment.Center
        ) {
            ShopHighLights()
        }

        Box(
            modifier = Modifier
                .layoutId("tabs")
                .padding(top = 16.dp, bottom = 8.dp)
        ) {
            UziaDefaultScrollableTabs(
                selectedTabIndex = tabIndex.value,
                onClick = { tabIndex.value = it })
        }

        Surface(
            modifier = Modifier
                .layoutId("back")
                .clip(RoundedCornerShape(topStart = 42.dp, topEnd = 42.dp)),
            color = LightColors.lightTealColor
        ) {
            Box(
                modifier = Modifier.padding(horizontal = 16.dp, vertical = 24.dp),
                contentAlignment = Alignment.TopCenter
            ) {
                ActionStatusComponent()
            }
        }

        Surface(
            modifier = Modifier
                .layoutId("front")
                .fillMaxSize()
                .clip(RoundedCornerShape(topStart = 42.dp, topEnd = 42.dp)),
            color = LightColors.lightDebianColor
        ) {
            // 动画完成前禁用列表滚动,完成后恢复
            FoodGridList(
                columns = 2,
                modifier = Modifier.userScrollEnabled(motionState.progress == 1f)
            )
        }
    }
}

关键逻辑说明

  1. 修正锚点配置:将MotionScene的滑动锚点绑定到实际包含LazyColumn的front组件,确保MotionLayout能正确响应该区域的滑动;
  2. 嵌套滚动拦截:通过NestedScrollConnection在动画未完成时,拦截向上滑动事件并交给MotionLayout处理,避免LazyColumn抢占事件;
  3. 滚动权限控制:利用motionState.progress判断动画是否完成,动态启用/禁用LazyColumn的滚动功能,确保动画过程中列表无法滚动。

内容的提问来源于stack exchange,提问作者Fredrick Reuben

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 17:10:45