Skip to content

Commit

Permalink
Merge changes Ibe6e88f1,Idddb5fe8 into androidx-main
Browse files Browse the repository at this point in the history
* changes:
  Changes AnchoredDraggable to operate based on small sliding window
  Add debug anchor visualization sample
  • Loading branch information
Treehugger Robot authored and Gerrit Code Review committed Mar 5, 2024
2 parents 67fe530 + ea1c7b8 commit 403ae82
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 143 deletions.
7 changes: 5 additions & 2 deletions compose/foundation/foundation/api/current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,12 @@ package androidx.compose.foundation.gestures {
method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> getDecayAnimationSpec();
method public float getLastVelocity();
method public float getOffset();
method @FloatRange(from=0.0, to=1.0) public float getProgress();
method @Deprecated @FloatRange(from=0.0, to=1.0) public float getProgress();
method public T getSettledValue();
method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getSnapAnimationSpec();
method public T getTargetValue();
method public boolean isAnimationRunning();
method @FloatRange(from=0.0, to=1.0) public float progress(T from, T to);
method public float requireOffset();
method public suspend Object? settle(float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
method public void updateAnchors(androidx.compose.foundation.gestures.DraggableAnchors<T> newAnchors, optional T newTarget);
Expand All @@ -423,7 +425,8 @@ package androidx.compose.foundation.gestures {
property public final boolean isAnimationRunning;
property public final float lastVelocity;
property public final float offset;
property @FloatRange(from=0.0, to=1.0) public final float progress;
property @Deprecated @FloatRange(from=0.0, to=1.0) public final float progress;
property public final T settledValue;
property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec;
property public final T targetValue;
field public static final androidx.compose.foundation.gestures.AnchoredDraggableState.Companion Companion;
Expand Down
7 changes: 5 additions & 2 deletions compose/foundation/foundation/api/restricted_current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -412,10 +412,12 @@ package androidx.compose.foundation.gestures {
method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> getDecayAnimationSpec();
method public float getLastVelocity();
method public float getOffset();
method @FloatRange(from=0.0, to=1.0) public float getProgress();
method @Deprecated @FloatRange(from=0.0, to=1.0) public float getProgress();
method public T getSettledValue();
method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getSnapAnimationSpec();
method public T getTargetValue();
method public boolean isAnimationRunning();
method @FloatRange(from=0.0, to=1.0) public float progress(T from, T to);
method public float requireOffset();
method public suspend Object? settle(float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
method public void updateAnchors(androidx.compose.foundation.gestures.DraggableAnchors<T> newAnchors, optional T newTarget);
Expand All @@ -425,7 +427,8 @@ package androidx.compose.foundation.gestures {
property public final boolean isAnimationRunning;
property public final float lastVelocity;
property public final float offset;
property @FloatRange(from=0.0, to=1.0) public final float progress;
property @Deprecated @FloatRange(from=0.0, to=1.0) public final float progress;
property public final T settledValue;
property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec;
property public final T targetValue;
field public static final androidx.compose.foundation.gestures.AnchoredDraggableState.Companion Companion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.foundation.samples.AnchoredDraggableAnchorsFromCompositi
import androidx.compose.foundation.samples.AnchoredDraggableCatchAnimatingWidgetSample
import androidx.compose.foundation.samples.AnchoredDraggableCustomAnchoredSample
import androidx.compose.foundation.samples.AnchoredDraggableLayoutDependentAnchorsSample
import androidx.compose.foundation.samples.AnchoredDraggableProgressSample
import androidx.compose.foundation.samples.AnchoredDraggableWithOverscrollSample
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
Expand All @@ -44,5 +45,7 @@ fun AnchoredDraggableDemo() {
AnchoredDraggableCustomAnchoredSample()
Spacer(Modifier.height(50.dp))
AnchoredDraggableWithOverscrollSample()
Spacer(Modifier.height(50.dp))
AnchoredDraggableProgressSample()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,39 @@ import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.gestures.anchoredDraggable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.overscroll
import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.Center
import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.End
import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.HalfEnd
import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.HalfStart
import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.Start
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import kotlin.math.max
import kotlin.math.roundToInt

private enum class AnchoredDraggableSampleValue {
Start, Center, End
Start, HalfStart, Center, HalfEnd, End
}

@Composable
Expand All @@ -69,7 +83,7 @@ fun AnchoredDraggableAnchorsFromCompositionSample() {
)
) {
AnchoredDraggableState(
initialValue = AnchoredDraggableSampleValue.Center,
initialValue = Center,
positionalThreshold,
velocityThreshold,
snapAnimationSpec,
Expand All @@ -83,9 +97,9 @@ fun AnchoredDraggableAnchorsFromCompositionSample() {
SideEffect {
state.updateAnchors(
DraggableAnchors {
AnchoredDraggableSampleValue.Start at 0f
AnchoredDraggableSampleValue.Center at containerWidthPx / 2f
AnchoredDraggableSampleValue.End at containerWidthPx
Start at 0f
Center at containerWidthPx / 2f
End at containerWidthPx
}
)
}
Expand Down Expand Up @@ -124,14 +138,14 @@ fun AnchoredDraggableLayoutDependentAnchorsSample() {
)
) {
AnchoredDraggableState(
initialValue = AnchoredDraggableSampleValue.Center,
initialValue = Center,
positionalThreshold,
velocityThreshold,
snapAnimationSpec,
decayAnimationSpec
)
}
val draggableSize = 100.dp
val draggableSize = 60.dp
val draggableSizePx = with(LocalDensity.current) { draggableSize.toPx() }
Box(
Modifier
Expand All @@ -142,16 +156,19 @@ fun AnchoredDraggableLayoutDependentAnchorsSample() {
val dragEndPoint = layoutSize.width - draggableSizePx
state.updateAnchors(
DraggableAnchors {
AnchoredDraggableSampleValue.Start at 0f
AnchoredDraggableSampleValue.Center at dragEndPoint / 2f
AnchoredDraggableSampleValue.End at dragEndPoint
Start at 0f
HalfStart at dragEndPoint * .25f
Center at dragEndPoint * .5f
HalfEnd at dragEndPoint * .75f
End at dragEndPoint
}
)
}
.visualizeDraggableAnchors(state, Orientation.Horizontal)
) {
Box(
Modifier
.size(100.dp)
.size(draggableSize)
.offset {
IntOffset(
x = state
Expand Down Expand Up @@ -205,7 +222,7 @@ fun AnchoredDraggableCatchAnimatingWidgetSample() {
// or drag the settling box.
val snapAnimationSpec = tween<Float>(durationMillis = 3000)
val state = AnchoredDraggableState(
initialValue = AnchoredDraggableSampleValue.Start,
initialValue = Start,
positionalThreshold = { distance: Float -> distance * 0.5f },
velocityThreshold = { with(density) { 125.dp.toPx() } },
snapAnimationSpec = snapAnimationSpec,
Expand All @@ -221,8 +238,8 @@ fun AnchoredDraggableCatchAnimatingWidgetSample() {
val dragEndPoint = layoutSize.width - draggableSizePx
state.updateAnchors(
DraggableAnchors {
AnchoredDraggableSampleValue.Start at 0f
AnchoredDraggableSampleValue.End at dragEndPoint
Start at 0f
End at dragEndPoint
}
)
}
Expand Down Expand Up @@ -266,7 +283,7 @@ fun AnchoredDraggableWithOverscrollSample() {
)
) {
AnchoredDraggableState(
initialValue = AnchoredDraggableSampleValue.Center,
initialValue = Center,
positionalThreshold,
velocityThreshold,
animationSpec,
Expand All @@ -281,9 +298,9 @@ fun AnchoredDraggableWithOverscrollSample() {
val dragEndPoint = layoutSize.width - draggableSizePx
state.updateAnchors(
DraggableAnchors {
AnchoredDraggableSampleValue.Start at 0f
AnchoredDraggableSampleValue.Center at dragEndPoint / 2f
AnchoredDraggableSampleValue.End at dragEndPoint
Start at 0f
Center at dragEndPoint / 2f
End at dragEndPoint
}
)
}
Expand All @@ -309,3 +326,115 @@ fun AnchoredDraggableWithOverscrollSample() {
)
}
}

@Composable
fun AnchoredDraggableProgressSample() {
val density = LocalDensity.current
val snapAnimationSpec = tween<Float>()
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val positionalThreshold = { distance: Float -> distance * 0.5f }
val velocityThreshold = { with(density) { 125.dp.toPx() } }
val state = rememberSaveable(
density,
saver = AnchoredDraggableState.Saver(
snapAnimationSpec,
decayAnimationSpec,
positionalThreshold,
velocityThreshold
)
) {
AnchoredDraggableState(
initialValue = Center,
positionalThreshold,
velocityThreshold,
snapAnimationSpec,
decayAnimationSpec
)
}
val draggableSize = 60.dp
val draggableSizePx = with(LocalDensity.current) { draggableSize.toPx() }
Column(
Modifier
.fillMaxWidth()
// Our anchors depend on this box's size, so we obtain the size from onSizeChanged and
// use updateAnchors to let the state know about the new anchors
.onSizeChanged { layoutSize ->
val dragEndPoint = layoutSize.width - draggableSizePx
state.updateAnchors(
DraggableAnchors {
Start at 0f
Center at dragEndPoint * .5f
End at dragEndPoint
}
)
}
) {
// Read progress in a snapshot-backed context to receive updates. This could be e.g. a
// derived state, snapshotFlow or other snapshot-aware context like the graphicsLayer
// block.
val centerToStartProgress by derivedStateOf { state.progress(from = Center, to = Start) }
val centerToEndProgress by derivedStateOf { state.progress(from = Center, to = End) }
Box {
Box(
Modifier
.fillMaxWidth()
.height(draggableSize)
.graphicsLayer { alpha = max(centerToStartProgress, centerToEndProgress) }
.background(Color.Black)
)
Box(
Modifier
.size(draggableSize)
.offset {
IntOffset(
x = state
.requireOffset()
.roundToInt(), y = 0
)
}
.anchoredDraggable(state, Orientation.Horizontal)
.background(Color.Red)
)
}
}
}

/**
* A [Modifier] that visualizes the anchors attached to an [AnchoredDraggableState] as lines along
* the cross axis of the layout (start to end for [Orientation.Vertical], top to end for
* [Orientation.Horizontal]).
* This is useful to debug components with a complex set of anchors, or for AnchoredDraggable
* development.
*
* @param state The state whose anchors to visualize
* @param orientation The orientation of the [anchoredDraggable]
* @param lineColor The color of the visualization lines
* @param lineStrokeWidth The stroke width of the visualization lines
* @param linePathEffect The path effect used to draw the visualization lines
*/
private fun Modifier.visualizeDraggableAnchors(
state: AnchoredDraggableState<*>,
orientation: Orientation,
lineColor: Color = Color.Black,
lineStrokeWidth: Float = 10f,
linePathEffect: PathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 30f))
) = drawWithContent {
drawContent()
state.anchors.forEach { _, position ->
val startOffset = Offset(
x = if (orientation == Orientation.Horizontal) position else 0f,
y = if (orientation == Orientation.Vertical) position else 0f
)
val endOffset = Offset(
x = if (orientation == Orientation.Horizontal) startOffset.x else size.height,
y = if (orientation == Orientation.Vertical) startOffset.y else size.width
)
drawLine(
color = lineColor,
start = startOffset,
end = endOffset,
strokeWidth = lineStrokeWidth,
pathEffect = linePathEffect
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ class AnchoredDraggableGestureTest {
)
val anchors = DraggableAnchors {
A at 0f
B at 250f
C at 500f
B at AnchoredDraggableBoxSize.value / 2f
C at AnchoredDraggableBoxSize.value
}
state.updateAnchors(anchors)

Expand Down Expand Up @@ -164,8 +164,8 @@ class AnchoredDraggableGestureTest {
)
val anchors = DraggableAnchors {
A at 0f
B at 250f
C at 500f
B at AnchoredDraggableBoxSize.value / 2f
C at AnchoredDraggableBoxSize.value
}
state.updateAnchors(anchors)

Expand Down Expand Up @@ -870,7 +870,7 @@ class AnchoredDraggableGestureTest {
val velocityThreshold = 100.dp
val state = AnchoredDraggableState(
initialValue = A,
velocityThreshold = { with(rule.density) { velocityThreshold.toPx() } },
velocityThreshold = { 0f },
positionalThreshold = { Float.POSITIVE_INFINITY },
snapAnimationSpec = tween(),
decayAnimationSpec = DefaultDecayAnimationSpec
Expand Down Expand Up @@ -909,13 +909,13 @@ class AnchoredDraggableGestureTest {
.performTouchInput {
swipeWithVelocity(
start = Offset(left, 0f),
end = Offset(right / 2, 0f),
end = Offset(right / 4, 0f),
endVelocity = with(rule.density) { velocityThreshold.toPx() } * 0.9f
)
}

rule.waitForIdle()
assertThat(state.currentValue).isEqualTo(A)
assertThat(state.settledValue).isEqualTo(A)
}

@Test
Expand Down
Loading

0 comments on commit 403ae82

Please sign in to comment.