https://kotlinlang.org logo
#compose
Title
# compose
n

Nicolai

03/25/2024, 11:11 AM
Would appreciate some help using on this issue using AnchoredDraggableState. I’m trying to ‘reset’ my items to their start position when a user scrolls in the LazyColumn in which they reside. However, nothing happens upon scrolling. I’ve checked that the LaunchedEffect indeed runs when scrolling but the state is not snapped back. Can someone tell me what I’m doing wrong?
Copy code
val coroutineScope = rememberCoroutineScope()

var endPosition by remember {
    mutableFloatStateOf(-500f)
}

val density = LocalDensity.current
val state = remember(endPosition) {
    AnchoredDraggableState(
        initialValue = DragAnchors.Start,
        positionalThreshold = { distance: Float -> distance * 0.5f },
        velocityThreshold = { with(density) { 50.dp.toPx() } },
        animationSpec = tween(),
    ).apply {
        updateAnchors(
            DraggableAnchors {
                DragAnchors.Start at 0f
                DragAnchors.End at endPosition
            }
        )
    }
}

// Snap the position back to Start when user scrolls
LaunchedEffect(scrollState) {
    snapshotFlow { scrollState.firstVisibleItemIndex }
        .debounce { 500L }
        .collect { index ->
            coroutineScope.launch {
                state.snapTo(DragAnchors.Start)
            }
        }
}
j

jossiwolf

03/25/2024, 2:37 PM
Are you mutating your
endPosition
?
The effect isn't keyed against the
AnchoredDraggableState
and might hold an outdated reference
Generally, if your
endPosition
is mutable it's advisable to have that
updateAnchors
in a side effect instead of the
remember
n

Nicolai

03/26/2024, 11:36 AM
Thanks for taking your time to answer 🙏 . The endPosition stuff works and I don’t think that’s related. I simply want all items that are at their DragAnchors.End to snap back to DragAnchors.Start when the user scrolls Which should be the last part of the code. Also here is the full version:
Copy code
@OptIn(ExperimentalFoundationApi::class, FlowPreview::class)
@Composable
private fun DraggableContainer(
    content: @Composable () -> Unit = {},
    offScreenContent: @Composable RowScope.() -> Unit = {},
    scrollState: LazyListState,
) {
    val coroutineScope = rememberCoroutineScope()

    var endPosition by remember {
        mutableFloatStateOf(-500f)
    }

    val density = LocalDensity.current
    val anchors = remember {
        DraggableAnchors {
            DragAnchors.Start at 0f
            DragAnchors.End at endPosition
        }
    }
    val state = remember(endPosition) {
        AnchoredDraggableState(
            anchors = anchors,
            initialValue = DragAnchors.Start,
            positionalThreshold = { distance: Float -> distance * 0.5f },
            velocityThreshold = { with(density) { 50.dp.toPx() } },
            animationSpec = tween(),
        )
    }
    

    LaunchedEffect(scrollState) {
        snapshotFlow { scrollState.firstVisibleItemIndex }
            .debounce { 500L }
            .collect { index ->
                coroutineScope.launch {
                    state.snapTo(DragAnchors.Start)
                }
            }
    }

    Box(modifier = Modifier
        .offset {
            IntOffset(
                x = state
                    .requireOffset()
                    .roundToInt(),
                y = 0,
            )
        }
        .anchoredDraggable(state, Orientation.Horizontal)) {

        Layout(
            modifier = Modifier
                .fillMaxWidth()
                .wrapContentHeight(),
            content = {
                content()
                Row(modifier = Modifier.padding(horizontal = 16.dp)) {
                    offScreenContent()
                }

            },
            measurePolicy = { measurables, constraints ->
                val placeables = measurables.mapIndexed { index, measurable ->
                    if (index == 0) {
                        measurable.measure(constraints)
                    } else {
                        val offScreenPlaceable = measurable.measure(Constraints())
                        endPosition = -offScreenPlaceable.width.toFloat() - 16.dp.roundToPx()
                        offScreenPlaceable
                    }
                }

                val height = placeables.maxOf { it.height }
                layout(constraints.maxWidth, height) {
                    placeables.forEachIndexed { index, placeable ->
                        if (index == 0) {
                            placeable.place(0, 0)
                        } else {
                            placeable.place(
                                constraints.maxWidth + 16.dp.roundToPx(),
                                height / 2 - placeable.height / 2
                            )
                        }
                    }
                }
            }
        )
    }
}
j

jossiwolf

03/26/2024, 2:01 PM
Right, you're mutating
endPosition
which would cause you to re-create the
AnchoredDraggableState
as its remember block is keyed against
endPosition
. If your anchors actually depend on a value from layout (which they seem to do), update them from layout.
We have a sample here: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]eSample.kt;l=157;drc=5c108158e6cf2e03dcd4523e9fcf4230514e8a58 You wouldn't want to use
onSizeChanged
but just replace your layout back-write with
updateAnchors
n

Nicolai

03/26/2024, 2:03 PM
Thank you I’ll take a look at this! 🙏
3 Views