android-jetpack-composeandroid-constraintlayoutandroid-motionlayoutandroid-jetpack-compose-lazy-column

Issue with MotionLayout animation not working properly with LazyColumn in Jetpack Compose


I'm trying to implement a MotionLayout animation with swipe gesture using the androidx.constraintlayout.compose version 1.1.0-alpha09 library in Jetpack Compose. However, I'm facing an issue where the animation doesn't work as expected when using LazyColumn inside the LayoutMation and swipe on LazyColumn.

Here is my compose

@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(
        motionScene = MotionScene(content = motionScene),
        motionLayoutState = motionState,
        modifier = Modifier.fillMaxSize(),
    ) {
        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)
        }
    }
}

Here is the motion scene

{
  ConstraintSets: {
    start: {
      hero: {
        width: "spread",
        top: ["parent", "top"],
        start: ["parent", "start"],
        end: ["parent", "end"],
        translationY: 0,
        alpha: 1,
        scaleX: 1,
        scaleY: 1,
      },
      content: {
        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,
      },
      content: {
        width: "spread",
        height: "spread",
        start: ["parent", "start"],
        end: ["parent", "end"],
        top: ["parent","top"],
        bottom: ["parent","bottom"],
      },
    },
  },
  Transitions: {
    default: {
      from: "start",
      to: "end",
      onSwipe: {
        anchor: "content",
        direction: "bottom",
        side: "top",
      },
    }
  }
}

The issue seems to be related to how Jetpack Compose prioritizes the scroll event and swipe event. I've tried disabling and enabling the userScrollEnabled property of LazyColumn, but it doesn't provide the desired behavior. What I'm trying to achieve is an animation where the LazyColumn doesn't scroll until the animation completes. After the animation finishes, the LazyColumn should resume scrolling if the user continues swiping.

I suspect that the touch events are being intercepted or conflicting between LazyColumn and MotionLayout, causing the animation to not work as expected within the LazyColumn.

I'm looking for suggestions or insights on how to properly handle the interaction between MotionLayout and LazyColumn to achieve the desired animation behavior. I want the LazyColumn to pause scrolling during the animation and resume scrolling if the user continues swiping after the animation completes.

Any help or guidance would be greatly appreciated. Thank you!


Solution

  • You need to have the swipe drive the progress of the motionLayout. Those connections need to be a programmatic and more explicit in compose. The key pieces of code are a NestedScrollConnection and a Box containing the LazyColumn:

        val nestedScrollConnection = remember {
        object : NestedScrollConnection {
            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                val height = toolbarHeight.value;
    
                if (height + available.y > maxPx) {
                    toolbarHeight.value = maxPx
                    return Offset(0f, maxPx - height)
                }
    
                if (height + available.y < minPx) {
                    toolbarHeight.value = minPx
                    return Offset(0f, minPx - height)
                }
    
                toolbarHeight.value += available.y
                return Offset(0f, available.y)
            }
    
        }
    }
    val progress = = 1 - (toolbarHeight.value - minPx) / (maxPx - minPx);
     MotionLayout(
            ...
            progress = progress
        )
     Box( Modifier .nestedScroll(nestedScrollConnection)) {
            LazyColumn() {
                 
                }
            }
        }
    

    Working code can be found here:

    https://github.com/androidx/constraintlayout/tree/main/demoProjects/ExamplesComposeMotionLayout#motion-layout-as-collapsing-toolbar-for-lazy-column