feat(MM): Make MotionSpec declarative and state-driven (3/3)
This change refactors `MotionValue` to make its `MotionSpec` (animation
physics) derived from a reactive lambda, replacing the previous
imperative approach.
Previously, `MotionValue` had a mutable `spec` property that consumers
would update directly. This required imperative logic within consumers
(`motionValue.spec = ...`) which did not align well with Compose's
declarative, state-driven architecture.
The `MotionValue` constructor now accepts a `spec: () -> MotionSpec`
lambda. The value now automatically reacts to changes in any state read
within this lambda, making its behavior inherently declarative.
Test: Tested on the previous MotionValueTests
Bug: 428886057
Flag: com.android.systemui.scene_container
Change-Id: I83c22591994cab1a65014d6d17543c37ce319b61
diff --git a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/DirectionChangeDemo.kt b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/DirectionChangeDemo.kt
index dbb115f..fe12834 100644
--- a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/DirectionChangeDemo.kt
+++ b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/DirectionChangeDemo.kt
@@ -51,15 +51,14 @@
import com.android.mechanics.demo.tuneable.HasMotionValueVisualization
import com.android.mechanics.effects.FixedValue
import com.android.mechanics.rememberDistanceGestureContext
+import com.android.mechanics.rememberMotionSpecAsState
import com.android.mechanics.rememberMotionValue
import com.android.mechanics.spec.Mapping
-import com.android.mechanics.spec.MotionSpec
-import com.android.mechanics.spec.builder.rememberMotionBuilderContext
import com.android.mechanics.spec.builder.spatialMotionSpec
object DirectionChangeDemo : Demo<Unit>, HasMotionValueVisualization {
- var inputRange by mutableStateOf(0f..0f)
+ private var inputRange by mutableStateOf(0f..0f)
@Composable
override fun DemoUi(config: Unit, modifier: Modifier) {
@@ -67,8 +66,20 @@
// Also using GestureContext.dragOffset as input.
val gestureContext = rememberDistanceGestureContext()
- val spec = rememberSpec(inputOutputRange = inputRange)
- val motionValue = rememberMotionValue(gestureContext::dragOffset, { spec }, gestureContext)
+ val motionValue =
+ rememberMotionValue(
+ input = { gestureContext.dragOffset },
+ gestureContext = gestureContext,
+ spec =
+ rememberMotionSpecAsState {
+ spatialMotionSpec(baseMapping = Mapping.Fixed(inputRange.start)) {
+ after(
+ (inputRange.start + inputRange.endInclusive) / 2f,
+ FixedValue(inputRange.endInclusive),
+ )
+ }
+ },
+ )
Column(
verticalArrangement = Arrangement.spacedBy(24.dp),
@@ -134,22 +145,6 @@
}
}
- @Composable
- fun rememberSpec(inputOutputRange: ClosedFloatingPointRange<Float>): MotionSpec {
-
- val builderContext = rememberMotionBuilderContext()
- return remember(inputOutputRange, builderContext) {
- with(builderContext) {
- spatialMotionSpec(baseMapping = Mapping.Fixed(inputOutputRange.start)) {
- after(
- (inputOutputRange.start + inputOutputRange.endInclusive) / 2f,
- FixedValue(inputOutputRange.endInclusive),
- )
- }
- }
- }
- }
-
@Composable override fun rememberDefaultConfig() {}
override val visualizationInputRange: ClosedFloatingPointRange<Float>
diff --git a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/DirectionSpecDemo.kt b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/DirectionSpecDemo.kt
index 3896bcf..4a608f2 100644
--- a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/DirectionSpecDemo.kt
+++ b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/DirectionSpecDemo.kt
@@ -53,6 +53,7 @@
import com.android.mechanics.demo.tuneable.Demo
import com.android.mechanics.demo.tuneable.HasMotionValueVisualization
import com.android.mechanics.rememberDistanceGestureContext
+import com.android.mechanics.rememberMotionSpecAsState
import com.android.mechanics.rememberMotionValue
import com.android.mechanics.spec.Breakpoint
import com.android.mechanics.spec.BreakpointKey
@@ -62,7 +63,7 @@
import com.android.mechanics.spec.OnChangeSegmentHandler
import com.android.mechanics.spec.SegmentData
import com.android.mechanics.spec.SegmentKey
-import com.android.mechanics.spec.builder.rememberMotionBuilderContext
+import com.android.mechanics.spec.builder.MotionBuilderContext
import com.android.mechanics.spec.builder.spatialDirectionalMotionSpec
object DirectionSpecDemo : Demo<Unit>, HasMotionValueVisualization {
@@ -80,8 +81,12 @@
// Also using GestureContext.dragOffset as input.
val gestureContext = rememberDistanceGestureContext()
- val spec = rememberSpec(inputOutputRange = inputRange)
- val motionValue = rememberMotionValue(gestureContext::dragOffset, { spec }, gestureContext)
+ val motionValue =
+ rememberMotionValue(
+ input = { gestureContext.dragOffset },
+ gestureContext = gestureContext,
+ spec = rememberMotionSpecAsState { buildSpec(inputOutputRange = inputRange) },
+ )
Column(
verticalArrangement = Arrangement.spacedBy(24.dp),
@@ -147,55 +152,46 @@
}
}
- @Composable
- fun rememberSpec(inputOutputRange: ClosedFloatingPointRange<Float>): MotionSpec {
+ private fun MotionBuilderContext.buildSpec(
+ inputOutputRange: ClosedFloatingPointRange<Float>
+ ): MotionSpec {
val delta = inputOutputRange.endInclusive - inputOutputRange.start
val startPosPx = inputOutputRange.start
val detachPosPx = delta * .4f
val attachPosPx = delta * .1f
- val builderContext = rememberMotionBuilderContext()
-
- return remember(inputOutputRange, builderContext) {
- with(builderContext) {
- val detachSpec =
- spatialDirectionalMotionSpec(initialMapping = Mapping.Zero) {
- fractionalInputFromCurrent(startPosPx, fraction = .3f, key = Keys.Start)
- identity(detachPosPx, key = Keys.Detach, spring = spatial.slow)
- }
-
- val attachSpec =
- spatialDirectionalMotionSpec(initialMapping = Mapping.Zero) {
- identity(attachPosPx, key = Keys.Detach, spring = spatial.fast)
- }
-
- val segmentHandlers =
- mapOf<SegmentKey, OnChangeSegmentHandler>(
- SegmentKey(Keys.Detach, Keys.End, InputDirection.Min) to
- { currentSegment, _, newDirection ->
- if (newDirection != currentSegment.direction) currentSegment
- else null
- },
- SegmentKey(Keys.Start, Keys.Detach, InputDirection.Max) to
- {
- currentSegment: SegmentData,
- newInput: Float,
- newDirection: InputDirection ->
- if (newDirection != currentSegment.direction && newInput >= 0)
- currentSegment
- else null
- },
- )
-
- MotionSpec(
- maxDirection = detachSpec,
- minDirection = attachSpec,
- resetSpring = spatial.default,
- segmentHandlers = segmentHandlers,
- )
+ val detachSpec =
+ spatialDirectionalMotionSpec(initialMapping = Mapping.Zero) {
+ fractionalInputFromCurrent(startPosPx, fraction = .3f, key = Keys.Start)
+ identity(detachPosPx, key = Keys.Detach, spring = spatial.slow)
}
- }
+
+ val attachSpec =
+ spatialDirectionalMotionSpec(initialMapping = Mapping.Zero) {
+ identity(attachPosPx, key = Keys.Detach, spring = spatial.fast)
+ }
+
+ val segmentHandlers =
+ mapOf<SegmentKey, OnChangeSegmentHandler>(
+ SegmentKey(Keys.Detach, Keys.End, InputDirection.Min) to
+ { currentSegment, _, newDirection ->
+ if (newDirection != currentSegment.direction) currentSegment else null
+ },
+ SegmentKey(Keys.Start, Keys.Detach, InputDirection.Max) to
+ { currentSegment: SegmentData, newInput: Float, newDirection: InputDirection ->
+ if (newDirection != currentSegment.direction && newInput >= 0)
+ currentSegment
+ else null
+ },
+ )
+
+ return MotionSpec(
+ maxDirection = detachSpec,
+ minDirection = attachSpec,
+ resetSpring = spatial.default,
+ segmentHandlers = segmentHandlers,
+ )
}
@Composable override fun rememberDefaultConfig() {}
diff --git a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/GuaranteeBoxDemo.kt b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/GuaranteeBoxDemo.kt
index 934d259..e1aafe9 100644
--- a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/GuaranteeBoxDemo.kt
+++ b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/GuaranteeBoxDemo.kt
@@ -58,10 +58,12 @@
import com.android.mechanics.demo.tuneable.HasMotionValueVisualization
import com.android.mechanics.demo.tuneable.SpringParameterSection
import com.android.mechanics.rememberDistanceGestureContext
+import com.android.mechanics.rememberMotionSpecAsState
import com.android.mechanics.rememberMotionValue
import com.android.mechanics.spec.Guarantee
import com.android.mechanics.spec.Mapping
import com.android.mechanics.spec.MotionSpec
+import com.android.mechanics.spec.builder.MotionBuilderContext
import com.android.mechanics.spec.builder.rememberMotionBuilderContext
import com.android.mechanics.spec.builder.spatialDirectionalMotionSpec
import com.android.mechanics.spring.SpringParameters
@@ -93,15 +95,21 @@
// Also using GestureContext.dragOffset as input.
val gestureContext = rememberDistanceGestureContext()
- val spec =
- rememberSpec(
- activeScenario,
- { placedBoxX },
- { placedBoxWidth },
- inputOutputRange = inputRange,
- config,
+
+ val motionValue =
+ rememberMotionValue(
+ input = { gestureContext.dragOffset },
+ gestureContext = gestureContext,
+ spec =
+ rememberMotionSpecAsState {
+ buildSpec(
+ scenario = activeScenario,
+ x = { placedBoxX },
+ width = { placedBoxWidth },
+ config = config,
+ )
+ },
)
- val motionValue = rememberMotionValue(gestureContext::dragOffset, { spec }, gestureContext)
Column(
verticalArrangement = Arrangement.spacedBy(24.dp),
modifier = modifier.fillMaxWidth().padding(vertical = 24.dp, horizontal = 48.dp),
@@ -202,63 +210,52 @@
}
}
- @Composable
- fun rememberSpec(
+ private fun MotionBuilderContext.buildSpec(
scenario: Scenario,
x: () -> Float,
width: () -> Float,
- inputOutputRange: ClosedFloatingPointRange<Float>,
config: Config,
): MotionSpec {
-
- val builderContext = rememberMotionBuilderContext()
val left = x()
val widthVal = width()
val right = left + widthVal
- return remember(scenario, inputOutputRange, config, left, widthVal, builderContext) {
- with(builderContext) {
- val guarantee = Guarantee.InputDelta(config.guaranteeDistance.toPx())
- val minSize = config.minVisibleWidth.toPx()
- when (scenario) {
- Scenario.Mapped ->
- MotionSpec(
- spatialDirectionalMotionSpec(initialMapping = Mapping.Zero) {
- target(breakpoint = left, from = 0f, to = widthVal)
- fixedValue(breakpoint = right, value = widthVal)
- }
- )
+ val guarantee = Guarantee.InputDelta(config.guaranteeDistance.toPx())
+ val minSize = config.minVisibleWidth.toPx()
- Scenario.Triggered ->
- MotionSpec(
- spatialDirectionalMotionSpec(initialMapping = Mapping.Zero) {
- target(
- breakpoint = min(left + minSize, right),
- from = minSize,
- to = widthVal - minSize,
- )
- fixedValue(breakpoint = right, value = widthVal)
- }
- )
+ return when (scenario) {
+ Scenario.Mapped ->
+ MotionSpec(
+ spatialDirectionalMotionSpec(initialMapping = Mapping.Zero) {
+ target(breakpoint = left, from = 0f, to = widthVal)
+ fixedValue(breakpoint = right, value = widthVal)
+ }
+ )
- Scenario.Guaranteed ->
- MotionSpec(
- spatialDirectionalMotionSpec(initialMapping = Mapping.Zero) {
- target(
- breakpoint = min(left + minSize, right),
- from = minSize,
- to = widthVal - minSize,
- guarantee = guarantee,
- )
- fixedValue(
- breakpoint = right,
- value = widthVal,
- guarantee = guarantee,
- )
- }
+ Scenario.Triggered ->
+ MotionSpec(
+ spatialDirectionalMotionSpec(initialMapping = Mapping.Zero) {
+ target(
+ breakpoint = min(left + minSize, right),
+ from = minSize,
+ to = widthVal - minSize,
)
- }
- }
+ fixedValue(breakpoint = right, value = widthVal)
+ }
+ )
+
+ Scenario.Guaranteed ->
+ MotionSpec(
+ spatialDirectionalMotionSpec(initialMapping = Mapping.Zero) {
+ target(
+ breakpoint = min(left + minSize, right),
+ from = minSize,
+ to = widthVal - minSize,
+ guarantee = guarantee,
+ )
+ fixedValue(breakpoint = right, value = widthVal, guarantee = guarantee)
+ }
+ )
}
}
diff --git a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/GuaranteeFadeDemo.kt b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/GuaranteeFadeDemo.kt
index 1d89fe7..d448fcb 100644
--- a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/GuaranteeFadeDemo.kt
+++ b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/GuaranteeFadeDemo.kt
@@ -47,11 +47,11 @@
import com.android.mechanics.demo.tuneable.Demo
import com.android.mechanics.demo.tuneable.HasMotionValueVisualization
import com.android.mechanics.rememberDistanceGestureContext
+import com.android.mechanics.rememberMotionSpecAsState
import com.android.mechanics.rememberMotionValue
import com.android.mechanics.spec.Guarantee
import com.android.mechanics.spec.MotionSpec
import com.android.mechanics.spec.builder.effectsDirectionalMotionSpec
-import com.android.mechanics.spec.builder.rememberMotionBuilderContext
object GuaranteeFadeDemo : Demo<Unit>, HasMotionValueVisualization {
@@ -67,15 +67,48 @@
// Also using GestureContext.dragOffset as input.
val gestureContext = rememberDistanceGestureContext()
- val spec = rememberSpec(inputOutputRange = inputRange, { 0f })
- val guaranteeSpec =
- rememberSpec(inputOutputRange = inputRange, guaranteeDistance::floatValue)
val withoutGuarantee =
- rememberMotionValue(gestureContext::dragOffset, { spec }, gestureContext)
+ rememberMotionValue(
+ input = { gestureContext.dragOffset },
+ gestureContext = gestureContext,
+ spec =
+ rememberMotionSpecAsState {
+ MotionSpec(
+ effectsDirectionalMotionSpec {
+ fixedValue(
+ breakpoint = (inputRange.start + inputRange.endInclusive) / 2f,
+ value = 1f,
+ guarantee = Guarantee.None,
+ )
+ }
+ )
+ },
+ )
val withGuarantee =
- rememberMotionValue(gestureContext::dragOffset, { guaranteeSpec }, gestureContext)
+ rememberMotionValue(
+ input = { gestureContext.dragOffset },
+ gestureContext = gestureContext,
+ spec =
+ rememberMotionSpecAsState {
+ val distance = guaranteeDistance.floatValue
+ MotionSpec(
+ effectsDirectionalMotionSpec {
+ fixedValue(
+ breakpoint = (inputRange.start + inputRange.endInclusive) / 2f,
+ value = 1f,
+ guarantee =
+ if (distance > 0) {
+ Guarantee.InputDelta(distance)
+ } else {
+ Guarantee.None
+ },
+ )
+ }
+ )
+ },
+ )
val defaultValueColor = MaterialTheme.colorScheme.primary
val guaranteeValueColor = MaterialTheme.colorScheme.secondary
@@ -136,31 +169,6 @@
}
}
- @Composable
- fun rememberSpec(
- inputOutputRange: ClosedFloatingPointRange<Float>,
- guaranteeDistance: () -> Float,
- ): MotionSpec {
- val distance = guaranteeDistance()
- val guarantee = if (distance > 0) Guarantee.InputDelta(distance) else Guarantee.None
- val builderContext = rememberMotionBuilderContext()
-
- return remember(guarantee, inputOutputRange, builderContext) {
- with(builderContext) {
- MotionSpec(
- effectsDirectionalMotionSpec {
- fixedValue(
- breakpoint =
- (inputOutputRange.start + inputOutputRange.endInclusive) / 2f,
- value = 1f,
- guarantee = guarantee,
- )
- }
- )
- }
- }
- }
-
@Composable override fun rememberDefaultConfig() {}
override val visualizationInputRange: ClosedFloatingPointRange<Float>
diff --git a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/MagneticDetachDemo.kt b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/MagneticDetachDemo.kt
index d902619..bfd70bb 100644
--- a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/MagneticDetachDemo.kt
+++ b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/MagneticDetachDemo.kt
@@ -52,8 +52,8 @@
import com.android.mechanics.demo.tuneable.HasMotionValueVisualization
import com.android.mechanics.effects.MagneticDetach
import com.android.mechanics.rememberDistanceGestureContext
+import com.android.mechanics.rememberMotionSpecAsState
import com.android.mechanics.rememberMotionValue
-import com.android.mechanics.spec.builder.rememberMotionBuilderContext
import com.android.mechanics.spec.builder.spatialMotionSpec
object MagneticDetachDemo : Demo<Unit>, HasMotionValueVisualization {
@@ -64,12 +64,15 @@
val colors = MaterialTheme.colorScheme
val gestureContext = rememberDistanceGestureContext()
- val motionBuilderContext = rememberMotionBuilderContext()
- val spec =
- remember(motionBuilderContext) {
- motionBuilderContext.spatialMotionSpec { after(50.dp.toPx(), MagneticDetach()) }
- }
- val motionValue = rememberMotionValue(gestureContext::dragOffset, { spec }, gestureContext)
+ val motionValue =
+ rememberMotionValue(
+ input = { gestureContext.dragOffset },
+ gestureContext = gestureContext,
+ spec =
+ rememberMotionSpecAsState {
+ spatialMotionSpec { after(50.dp.toPx(), MagneticDetach()) }
+ },
+ )
Column(
verticalArrangement = Arrangement.spacedBy(24.dp),
diff --git a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/MagneticDetachWithOverdragDemo.kt b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/MagneticDetachWithOverdragDemo.kt
index 0c05ff2..1db79ab 100644
--- a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/MagneticDetachWithOverdragDemo.kt
+++ b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/MagneticDetachWithOverdragDemo.kt
@@ -52,12 +52,12 @@
import com.android.mechanics.effects.MagneticDetach
import com.android.mechanics.effects.Overdrag
import com.android.mechanics.rememberDistanceGestureContext
+import com.android.mechanics.rememberMotionSpecAsState
import com.android.mechanics.rememberMotionValue
import com.android.mechanics.spec.InputDirection
import com.android.mechanics.spec.SemanticKey
import com.android.mechanics.spec.builder.MotionBuilderContext
import com.android.mechanics.spec.builder.fixedSpatialValueSpec
-import com.android.mechanics.spec.builder.rememberMotionBuilderContext
import com.android.mechanics.spec.builder.spatialMotionSpec
object MagneticDetachWithOverdragDemo : Demo<Unit>, HasMotionValueVisualization {
@@ -68,10 +68,20 @@
override fun DemoUi(config: Unit, modifier: Modifier) {
val colors = MaterialTheme.colorScheme
val gestureContext = rememberDistanceGestureContext()
- val motionBuilderContext = rememberMotionBuilderContext()
- var spec by remember() { mutableStateOf(motionBuilderContext.fixedSpatialValueSpec(0f)) }
+ var dragState: DragState by remember { mutableStateOf(DragState.Idle(targetValue = 0f)) }
- val motionValue = rememberMotionValue(gestureContext::dragOffset, { spec }, gestureContext)
+ val motionValue =
+ rememberMotionValue(
+ input = { gestureContext.dragOffset },
+ gestureContext = gestureContext,
+ spec =
+ rememberMotionSpecAsState {
+ when (val dragState = dragState) {
+ is DragState.Idle -> fixedSpatialValueSpec(dragState.targetValue)
+ DragState.Dragging -> createDragSpec()
+ }
+ },
+ )
Column(
verticalArrangement = Arrangement.spacedBy(24.dp),
@@ -110,11 +120,11 @@
Orientation.Horizontal,
onDragStarted = {
gestureContext.reset(motionValue.output, InputDirection.Max)
- spec = motionBuilderContext.createDragSpec()
+ dragState = DragState.Dragging
},
onDragStopped = {
val targetValue = motionValue[TargetValue] ?: motionValue.output
- spec = motionBuilderContext.fixedSpatialValueSpec(targetValue)
+ dragState = DragState.Idle(targetValue = targetValue)
},
)
.debugMotionValue(motionValue)
@@ -135,6 +145,12 @@
override val identifier: String = "MagneticDetachOverdrag"
val TargetValue = SemanticKey<Float?>()
+
+ private sealed interface DragState {
+ data class Idle(val targetValue: Float) : DragState
+
+ data object Dragging : DragState
+ }
}
private fun MotionBuilderContext.createDragSpec() = spatialMotionSpec {
diff --git a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/SpecDemo.kt b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/SpecDemo.kt
index 4a5c981..43b0444 100644
--- a/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/SpecDemo.kt
+++ b/samples/MotionMechanics/src/com/android/mechanics/demo/presentation/SpecDemo.kt
@@ -55,12 +55,13 @@
import com.android.mechanics.demo.tuneable.HasMotionValueVisualization
import com.android.mechanics.demo.tuneable.LabelledCheckbox
import com.android.mechanics.rememberDistanceGestureContext
+import com.android.mechanics.rememberMotionSpecAsState
import com.android.mechanics.rememberMotionValue
import com.android.mechanics.spec.DirectionalMotionSpec
import com.android.mechanics.spec.Guarantee
import com.android.mechanics.spec.Mapping
import com.android.mechanics.spec.MotionSpec
-import com.android.mechanics.spec.builder.rememberMotionBuilderContext
+import com.android.mechanics.spec.builder.MotionBuilderContext
import com.android.mechanics.spec.builder.spatialDirectionalMotionSpec
object SpecDemo : Demo<SpecDemo.Config>, HasMotionValueVisualization {
@@ -82,8 +83,15 @@
// Also using GestureContext.dragOffset as input.
val gestureContext = rememberDistanceGestureContext()
- val spec = rememberSpec(activeScenario, config, inputOutputRange = inputRange)
- val motionValue = rememberMotionValue(gestureContext::dragOffset, { spec }, gestureContext)
+ val motionValue =
+ rememberMotionValue(
+ input = { gestureContext.dragOffset },
+ gestureContext = gestureContext,
+ spec =
+ rememberMotionSpecAsState {
+ buildSpec(activeScenario, config, inputOutputRange = inputRange)
+ },
+ )
Column(
verticalArrangement = Arrangement.spacedBy(24.dp),
@@ -153,75 +161,59 @@
}
}
- @Composable
- fun rememberSpec(
+ private fun MotionBuilderContext.buildSpec(
scenario: Scenario,
config: Config,
inputOutputRange: ClosedFloatingPointRange<Float>,
): MotionSpec {
+ return MotionSpec(
+ when (scenario) {
+ Scenario.Empty -> DirectionalMotionSpec.Empty
+ Scenario.Toggle ->
+ spatialDirectionalMotionSpec(Mapping.Fixed(inputOutputRange.start)) {
+ fixedValue(
+ breakpoint =
+ (inputOutputRange.start + inputOutputRange.endInclusive) / 2f,
+ value = inputOutputRange.endInclusive,
+ )
+ }
- val builderContext = rememberMotionBuilderContext()
+ Scenario.Steps ->
+ spatialDirectionalMotionSpec(Mapping.Fixed(inputOutputRange.start)) {
+ val steps = 8
+ val stepSize =
+ (inputOutputRange.start + inputOutputRange.endInclusive) / steps
- return remember(scenario, inputOutputRange, config, builderContext) {
- MotionSpec(
- when (scenario) {
- Scenario.Empty -> DirectionalMotionSpec.Empty
- Scenario.Toggle ->
- builderContext.spatialDirectionalMotionSpec(
- Mapping.Fixed(inputOutputRange.start)
- ) {
+ val guarantee =
+ if (config.stepGuarantee) Guarantee.InputDelta(stepSize)
+ else Guarantee.None
+
+ val outDiff =
+ (inputOutputRange.start + inputOutputRange.endInclusive) / (steps - 1)
+ repeat(steps - 2) { step ->
fixedValue(
- breakpoint =
- (inputOutputRange.start + inputOutputRange.endInclusive) / 2f,
- value = inputOutputRange.endInclusive,
- )
- }
-
- Scenario.Steps ->
- builderContext.spatialDirectionalMotionSpec(
- Mapping.Fixed(inputOutputRange.start)
- ) {
- val steps = 8
- val stepSize =
- (inputOutputRange.start + inputOutputRange.endInclusive) / steps
-
- val guarantee =
- if (config.stepGuarantee) Guarantee.InputDelta(stepSize)
- else Guarantee.None
-
- val outDiff =
- (inputOutputRange.start + inputOutputRange.endInclusive) /
- (steps - 1)
- repeat(steps - 2) { step ->
- fixedValue(
- breakpoint = (step + 1) * stepSize,
- value = (step + 1) * outDiff,
- guarantee = guarantee,
- )
- }
-
- fixedValue(
- breakpoint = inputOutputRange.endInclusive - stepSize,
- value = inputOutputRange.endInclusive,
+ breakpoint = (step + 1) * stepSize,
+ value = (step + 1) * outDiff,
guarantee = guarantee,
)
}
- Scenario.TrackNSnap ->
- builderContext.spatialDirectionalMotionSpec(
- Mapping.Fixed(inputOutputRange.start)
- ) {
- val third = (inputOutputRange.start + inputOutputRange.endInclusive) / 3
+ fixedValue(
+ breakpoint = inputOutputRange.endInclusive - stepSize,
+ value = inputOutputRange.endInclusive,
+ guarantee = guarantee,
+ )
+ }
- target(third, from = third, to = 2 * third)
- fixedValue(
- breakpoint = 2 * third,
- value = inputOutputRange.endInclusive,
- )
- }
- }
- )
- }
+ Scenario.TrackNSnap ->
+ spatialDirectionalMotionSpec(Mapping.Fixed(inputOutputRange.start)) {
+ val third = (inputOutputRange.start + inputOutputRange.endInclusive) / 3
+
+ target(third, from = third, to = 2 * third)
+ fixedValue(breakpoint = 2 * third, value = inputOutputRange.endInclusive)
+ }
+ }
+ )
}
@Composable