Skip to content

Commit 14b6384

Browse files
authored
Add example usage of keyframesWithSplines (#430)
* Add example of keyframesWithSplines * Apply Spotless * PR Review comments. * PR Review comments. * Apply Spotless --------- Co-authored-by: riggaroo <[email protected]>
1 parent 5bf449b commit 14b6384

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed

compose/snippets/src/main/java/com/example/compose/snippets/animations/AnimationSnippets.kt

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ import androidx.compose.animation.EnterExitState
2626
import androidx.compose.animation.SizeTransform
2727
import androidx.compose.animation.animateColor
2828
import androidx.compose.animation.animateContentSize
29+
import androidx.compose.animation.core.Animatable
2930
import androidx.compose.animation.core.AnimationVector1D
3031
import androidx.compose.animation.core.AnimationVector2D
3132
import androidx.compose.animation.core.Easing
33+
import androidx.compose.animation.core.ExperimentalAnimationSpecApi
3234
import androidx.compose.animation.core.ExperimentalTransitionApi
3335
import androidx.compose.animation.core.FastOutLinearInEasing
3436
import androidx.compose.animation.core.FastOutSlowInEasing
@@ -43,11 +45,13 @@ import androidx.compose.animation.core.TwoWayConverter
4345
import androidx.compose.animation.core.VectorConverter
4446
import androidx.compose.animation.core.animateDp
4547
import androidx.compose.animation.core.animateFloatAsState
48+
import androidx.compose.animation.core.animateOffsetAsState
4649
import androidx.compose.animation.core.animateRect
4750
import androidx.compose.animation.core.animateValueAsState
4851
import androidx.compose.animation.core.createChildTransition
4952
import androidx.compose.animation.core.infiniteRepeatable
5053
import androidx.compose.animation.core.keyframes
54+
import androidx.compose.animation.core.keyframesWithSpline
5155
import androidx.compose.animation.core.rememberInfiniteTransition
5256
import androidx.compose.animation.core.rememberTransition
5357
import androidx.compose.animation.core.repeatable
@@ -71,11 +75,13 @@ import androidx.compose.foundation.Image
7175
import androidx.compose.foundation.background
7276
import androidx.compose.foundation.clickable
7377
import androidx.compose.foundation.layout.Box
78+
import androidx.compose.foundation.layout.BoxWithConstraints
7479
import androidx.compose.foundation.layout.Column
7580
import androidx.compose.foundation.layout.Row
7681
import androidx.compose.foundation.layout.fillMaxSize
7782
import androidx.compose.foundation.layout.fillMaxWidth
7883
import androidx.compose.foundation.layout.height
84+
import androidx.compose.foundation.layout.offset
7985
import androidx.compose.foundation.layout.padding
8086
import androidx.compose.foundation.layout.size
8187
import androidx.compose.foundation.layout.sizeIn
@@ -93,25 +99,34 @@ import androidx.compose.runtime.State
9399
import androidx.compose.runtime.getValue
94100
import androidx.compose.runtime.mutableIntStateOf
95101
import androidx.compose.runtime.mutableLongStateOf
102+
import androidx.compose.runtime.mutableStateListOf
96103
import androidx.compose.runtime.mutableStateOf
97104
import androidx.compose.runtime.remember
98105
import androidx.compose.runtime.setValue
99106
import androidx.compose.runtime.withFrameNanos
100107
import androidx.compose.ui.Alignment
101108
import androidx.compose.ui.Modifier
109+
import androidx.compose.ui.draw.drawBehind
110+
import androidx.compose.ui.geometry.Offset
102111
import androidx.compose.ui.geometry.Rect
103112
import androidx.compose.ui.graphics.Color
113+
import androidx.compose.ui.graphics.PathEffect
114+
import androidx.compose.ui.graphics.PointMode
104115
import androidx.compose.ui.graphics.graphicsLayer
105116
import androidx.compose.ui.layout.ContentScale
117+
import androidx.compose.ui.layout.boundsInParent
118+
import androidx.compose.ui.layout.onPlaced
106119
import androidx.compose.ui.platform.LocalDensity
107120
import androidx.compose.ui.tooling.preview.Preview
108121
import androidx.compose.ui.unit.Dp
109122
import androidx.compose.ui.unit.IntSize
110123
import androidx.compose.ui.unit.dp
124+
import androidx.compose.ui.unit.round
111125
import com.example.compose.snippets.R
112126
import java.text.BreakIterator
113127
import java.text.StringCharacterIterator
114128
import kotlinx.coroutines.delay
129+
import kotlinx.coroutines.isActive
115130

116131
/*
117132
* Copyright 2023 The Android Open Source Project
@@ -709,6 +724,101 @@ private fun AnimationSpecKeyframe() {
709724
// [END android_compose_animations_spec_keyframe]
710725
}
711726

727+
@OptIn(ExperimentalAnimationSpecApi::class)
728+
@Composable
729+
private fun AnimationSpecKeyframeWithSpline() {
730+
// [START android_compose_animation_spec_keyframes_with_spline]
731+
val offset by animateOffsetAsState(
732+
targetValue = Offset(300f, 300f),
733+
animationSpec = keyframesWithSpline {
734+
durationMillis = 6000
735+
Offset(0f, 0f) at 0
736+
Offset(150f, 200f) atFraction 0.5f
737+
Offset(0f, 100f) atFraction 0.7f
738+
}
739+
)
740+
// [END android_compose_animation_spec_keyframes_with_spline]
741+
}
742+
743+
@OptIn(ExperimentalAnimationSpecApi::class)
744+
@Preview
745+
@Composable
746+
private fun OffsetKeyframeWithSplineDemo() {
747+
val points = remember { mutableStateListOf<Offset>() }
748+
val offsetAnim = remember {
749+
Animatable(
750+
Offset.Zero,
751+
Offset.VectorConverter
752+
)
753+
}
754+
val density = LocalDensity.current
755+
756+
BoxWithConstraints(
757+
Modifier.fillMaxSize().drawBehind {
758+
drawPoints(
759+
points = points,
760+
pointMode = PointMode.Lines,
761+
color = Color.LightGray,
762+
strokeWidth = 4f,
763+
pathEffect = PathEffect.dashPathEffect(floatArrayOf(30f, 20f))
764+
)
765+
}
766+
) {
767+
val minDimension = minOf(maxWidth, maxHeight)
768+
val size = minDimension / 4
769+
770+
val sizePx = with(density) { size.toPx() }
771+
val widthPx = with(density) { maxWidth.toPx() }
772+
val heightPx = with(density) { maxHeight.toPx() }
773+
774+
val maxXOff = (widthPx - sizePx) / 2f
775+
val maxYOff = heightPx - (sizePx / 2f)
776+
777+
Box(
778+
Modifier.align(Alignment.TopCenter)
779+
.offset { offsetAnim.value.round() }
780+
.size(size)
781+
.background(Color.Red, RoundedCornerShape(50))
782+
.onPlaced { points.add(it.boundsInParent().center) }
783+
)
784+
785+
LaunchedEffect(Unit) {
786+
delay(1000)
787+
while (isActive) {
788+
offsetAnim.animateTo(
789+
targetValue = Offset.Zero,
790+
animationSpec =
791+
keyframesWithSpline {
792+
durationMillis = 4400
793+
794+
// Increasingly approach the halfway point moving from side to side
795+
for (i in 0..4) {
796+
val sign = if (i % 2 == 0) 1 else -1
797+
Offset(
798+
x = maxXOff * (i.toFloat() / 5f) * sign,
799+
y = (maxYOff) * (i.toFloat() / 5f)
800+
) atFraction (0.1f * i)
801+
}
802+
803+
// Halfway point (at bottom of the screen)
804+
Offset(0f, maxYOff) atFraction 0.5f
805+
806+
// Return with mirrored movement
807+
for (i in 0..4) {
808+
val sign = if (i % 2 == 0) 1 else -1
809+
Offset(
810+
x = maxXOff * (1f - i.toFloat() / 5f) * sign,
811+
y = (maxYOff) * (1f - i.toFloat() / 5f)
812+
) atFraction ((0.1f * i) + 0.5f)
813+
}
814+
}
815+
)
816+
points.clear()
817+
}
818+
}
819+
}
820+
}
821+
712822
@Composable
713823
private fun AnimationSpecRepeatable() {
714824
// [START android_compose_animations_spec_repeatable]

0 commit comments

Comments
 (0)