Nicolai
03/25/2024, 11:11 AMval 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)
}
}
}
jossiwolf
03/25/2024, 2:37 PMendPosition
?jossiwolf
03/25/2024, 2:38 PMAnchoredDraggableState
and might hold an outdated referencejossiwolf
03/25/2024, 2:38 PMendPosition
is mutable it's advisable to have that updateAnchors
in a side effect instead of the remember
Nicolai
03/26/2024, 11:36 AM@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
)
}
}
}
}
)
}
}
jossiwolf
03/26/2024, 2:01 PMendPosition
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.jossiwolf
03/26/2024, 2:02 PMonSizeChanged
but just replace your layout back-write with updateAnchors
Nicolai
03/26/2024, 2:03 PM