Fixed
Status Update
Comments
jo...@google.com <jo...@google.com> #2
Can you share your setup (specifically the anchors) with us?
da...@google.com <da...@google.com> #3
Sure thing: Here's the file I was using it in:
@Immutable
@JvmInline
value class CollapsingHeaderState internal constructor(
internal val packedValue: Long,
)
private fun CollapsingHeaderState(
translation: Float,
progress: Float,
) = CollapsingHeaderState(
packFloats(
val1 = translation,
val2 = progress,
),
)
val CollapsingHeaderState.translation
get() = unpackFloat1(packedValue)
val CollapsingHeaderState.progress
get() = unpackFloat2(packedValue)
private enum class CollapsingHeaderStatus {
Collapsed, Expanded
}
@Composable
@OptIn(ExperimentalFoundationApi::class)
fun CollapsingHeader(
collapsedHeight: Float,
onOffsetChanged: (CollapsingHeaderState) -> Unit = { },
headerContent: @Composable () -> Unit,
body: @Composable () -> Unit,
) {
var headerHeight by remember { mutableFloatStateOf(0f) }
val density = LocalDensity.current
val state = remember {
AnchoredDraggableState(
initialValue = CollapsingHeaderStatus.Collapsed,
positionalThreshold = { distance: Float -> distance * 0.5f },
velocityThreshold = { 100f },
animationSpec = tween(),
anchors = DraggableAnchors {
CollapsingHeaderStatus.Collapsed at with(density) { 500.dp.toPx() }
CollapsingHeaderStatus.Expanded at collapsedHeight
}
)
}
val connection = remember {
object : NestedScrollConnection {
override fun onPreScroll(
available: Offset,
source: NestedScrollSource
): Offset = when (val delta = available.y) {
in -Float.MAX_VALUE..-Float.MIN_VALUE -> state.dispatchRawDelta(delta).toOffset()
else -> Offset.Zero
}
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource
): Offset = state.dispatchRawDelta(
delta = available.y
).toOffset()
override suspend fun onPostFling(
consumed: Velocity,
available: Velocity
): Velocity {
state.settle(velocity = available.y)
return super.onPostFling(consumed, available)
}
}
}
Box {
Box(
modifier = Modifier
.pointerInput(Unit) {
detectVerticalDragGestures { _, dragAmount ->
state.dispatchRawDelta(dragAmount)
}
}
.onSizeChanged { headerHeight = it.height.toFloat() }
) {
headerContent()
}
Box(
modifier = Modifier
.offset {
IntOffset(
x = 0,
y = state.offset.roundToInt()
)
}
.anchoredDraggable(
state = state,
orientation = Orientation.Vertical
)
.nestedScroll(connection)
) {
body()
}
}
LaunchedEffect(headerHeight) {
state.updateAnchors(
DraggableAnchors {
CollapsingHeaderStatus.Collapsed at headerHeight
CollapsingHeaderStatus.Expanded at collapsedHeight
}
)
}
LaunchedEffect(state.offset, headerHeight, collapsedHeight) {
val translation = headerHeight - state.requireOffset()
val progress = translation / (headerHeight - collapsedHeight)
onOffsetChanged(
CollapsingHeaderState(
translation = translation,
progress = progress
)
)
}
}
private fun Float.toOffset() = Offset(0f, this)
Note for progress I had to calculate it as val progress = translation / (headerHeight - collapsedHeight)
.
Reading state.progress
in the effect would not update till after re-anchoring.
jo...@google.com <jo...@google.com> #4
This is essentially WAI. The progress
API is a bit odd, as it exposes the progress between currentValue
and targetValue
. What's happening here is that
currentValue
hasn't changed yettargetValue
is equal tocurrentValue
This highlights overall awkwardness in the API, so we're exposing a fun progress(from, to)
API instead.
ap...@google.com <ap...@google.com> #5
Project: platform/frameworks/support
Branch: androidx-main
commit ea1c7b82abfbd75b1396b1113852253e7b7dc444
Author: Jossi Wolf <jossiwolf@google.com>
Date: Sun Jan 28 12:29:03 2024
Changes AnchoredDraggable to operate based on small sliding window
Previously, AnchoredDraggable would operate based on `currentValue`, which could be several anchor points away from the current position of the offset. This made exposing APIs reflecting offset fractions tricky. The new behavior is aligned with Pager and subsequently allows us to expose a `currentValueOffsetFraction`.
Relnote: AnchoredDraggable's currentValue will now update when passing through an anchor point. Use settledValue to receive the previous currentValue semantics, only updating when settling at an anchor. The progress is now exposed as a function (requiring a starting and end point) instead of a property.
Test: Existing & refactored tests
Bug: 318707189, 298271489, 294991954
Change-Id: Ibe6e88f172b099e8f1f841722946471e4641f999
M compose/foundation/foundation/api/current.txt
M compose/foundation/foundation/api/restricted_current.txt
M compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt
M compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
M compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
M compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
https://android-review.googlesource.com/2930845
Branch: androidx-main
commit ea1c7b82abfbd75b1396b1113852253e7b7dc444
Author: Jossi Wolf <jossiwolf@google.com>
Date: Sun Jan 28 12:29:03 2024
Changes AnchoredDraggable to operate based on small sliding window
Previously, AnchoredDraggable would operate based on `currentValue`, which could be several anchor points away from the current position of the offset. This made exposing APIs reflecting offset fractions tricky. The new behavior is aligned with Pager and subsequently allows us to expose a `currentValueOffsetFraction`.
Relnote: AnchoredDraggable's currentValue will now update when passing through an anchor point. Use settledValue to receive the previous currentValue semantics, only updating when settling at an anchor. The progress is now exposed as a function (requiring a starting and end point) instead of a property.
Test: Existing & refactored tests
Bug: 318707189, 298271489, 294991954
Change-Id: Ibe6e88f172b099e8f1f841722946471e4641f999
M compose/foundation/foundation/api/current.txt
M compose/foundation/foundation/api/restricted_current.txt
M compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt
M compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
M compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
M compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
ap...@google.com <ap...@google.com> #6
Project: platform/frameworks/support
Branch: androidx-main
commit 5c108158e6cf2e03dcd4523e9fcf4230514e8a58
Author: Jossi Wolf <jossiwolf@google.com>
Date: Tue Mar 05 17:34:38 2024
Revert "Revert "Changes AnchoredDraggable to operate based on sm..."
Revert submission 2988649-revert-2930845-DMIZVAQUBO
Reason for revert: Re-landing since this doesn't include the API changes
Reverted changes: /q/submissionid:2988649-revert-2930845-DMIZVAQUBO
Relnote: AnchoredDraggable's currentValue will now update when passing through an anchor point. Use settledValue to receive the previous currentValue semantics, only updating when settling at an anchor. The progress is now exposed as a function (requiring a starting and end point) instead of a property.
Test: Existing & refactored tests
Bug: 318707189, 298271489, 294991954
Change-Id: I2ebd3677fd98f0d14f5d365a901cd1b36b0ce9e2
M compose/foundation/foundation/api/current.txt
M compose/foundation/foundation/api/restricted_current.txt
M compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt
M compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
M compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
M compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
https://android-review.googlesource.com/2989470
Branch: androidx-main
commit 5c108158e6cf2e03dcd4523e9fcf4230514e8a58
Author: Jossi Wolf <jossiwolf@google.com>
Date: Tue Mar 05 17:34:38 2024
Revert "Revert "Changes AnchoredDraggable to operate based on sm..."
Revert submission 2988649-revert-2930845-DMIZVAQUBO
Reason for revert: Re-landing since this doesn't include the API changes
Reverted changes: /q/submissionid:2988649-revert-2930845-DMIZVAQUBO
Relnote: AnchoredDraggable's currentValue will now update when passing through an anchor point. Use settledValue to receive the previous currentValue semantics, only updating when settling at an anchor. The progress is now exposed as a function (requiring a starting and end point) instead of a property.
Test: Existing & refactored tests
Bug: 318707189, 298271489, 294991954
Change-Id: I2ebd3677fd98f0d14f5d365a901cd1b36b0ce9e2
M compose/foundation/foundation/api/current.txt
M compose/foundation/foundation/api/restricted_current.txt
M compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/AnchoredDraggableDemo.kt
M compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
M compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
M compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
Description
Jetpack Compose version: androidx.compose.foundation:foundation:1.6.0-alpha02
Jetpack Compose component(s) used:
AnchoredDraggableState
Android Studio Build: Giraffe
Kotlin version: 1.8.10
Steps to Reproduce or Code Sample to Reproduce:
The
progress
property inAnchoredDraggable
state does not update when re-anchoring; it only updates during user interaction.In the snippet above, progress will only update when dragging a widget with the modifier applied. Once draging stops and reanchoring begins to the nearest anchor, the progress value does not update until the Composable has snapped back and settled.
This makes it impossible to animate smoothly as the progress snaps to the the end value instead of being updated continuously.