@@ -26,9 +26,11 @@ import androidx.compose.animation.EnterExitState
26
26
import androidx.compose.animation.SizeTransform
27
27
import androidx.compose.animation.animateColor
28
28
import androidx.compose.animation.animateContentSize
29
+ import androidx.compose.animation.core.Animatable
29
30
import androidx.compose.animation.core.AnimationVector1D
30
31
import androidx.compose.animation.core.AnimationVector2D
31
32
import androidx.compose.animation.core.Easing
33
+ import androidx.compose.animation.core.ExperimentalAnimationSpecApi
32
34
import androidx.compose.animation.core.ExperimentalTransitionApi
33
35
import androidx.compose.animation.core.FastOutLinearInEasing
34
36
import androidx.compose.animation.core.FastOutSlowInEasing
@@ -43,11 +45,13 @@ import androidx.compose.animation.core.TwoWayConverter
43
45
import androidx.compose.animation.core.VectorConverter
44
46
import androidx.compose.animation.core.animateDp
45
47
import androidx.compose.animation.core.animateFloatAsState
48
+ import androidx.compose.animation.core.animateOffsetAsState
46
49
import androidx.compose.animation.core.animateRect
47
50
import androidx.compose.animation.core.animateValueAsState
48
51
import androidx.compose.animation.core.createChildTransition
49
52
import androidx.compose.animation.core.infiniteRepeatable
50
53
import androidx.compose.animation.core.keyframes
54
+ import androidx.compose.animation.core.keyframesWithSpline
51
55
import androidx.compose.animation.core.rememberInfiniteTransition
52
56
import androidx.compose.animation.core.rememberTransition
53
57
import androidx.compose.animation.core.repeatable
@@ -71,11 +75,13 @@ import androidx.compose.foundation.Image
71
75
import androidx.compose.foundation.background
72
76
import androidx.compose.foundation.clickable
73
77
import androidx.compose.foundation.layout.Box
78
+ import androidx.compose.foundation.layout.BoxWithConstraints
74
79
import androidx.compose.foundation.layout.Column
75
80
import androidx.compose.foundation.layout.Row
76
81
import androidx.compose.foundation.layout.fillMaxSize
77
82
import androidx.compose.foundation.layout.fillMaxWidth
78
83
import androidx.compose.foundation.layout.height
84
+ import androidx.compose.foundation.layout.offset
79
85
import androidx.compose.foundation.layout.padding
80
86
import androidx.compose.foundation.layout.size
81
87
import androidx.compose.foundation.layout.sizeIn
@@ -93,25 +99,34 @@ import androidx.compose.runtime.State
93
99
import androidx.compose.runtime.getValue
94
100
import androidx.compose.runtime.mutableIntStateOf
95
101
import androidx.compose.runtime.mutableLongStateOf
102
+ import androidx.compose.runtime.mutableStateListOf
96
103
import androidx.compose.runtime.mutableStateOf
97
104
import androidx.compose.runtime.remember
98
105
import androidx.compose.runtime.setValue
99
106
import androidx.compose.runtime.withFrameNanos
100
107
import androidx.compose.ui.Alignment
101
108
import androidx.compose.ui.Modifier
109
+ import androidx.compose.ui.draw.drawBehind
110
+ import androidx.compose.ui.geometry.Offset
102
111
import androidx.compose.ui.geometry.Rect
103
112
import androidx.compose.ui.graphics.Color
113
+ import androidx.compose.ui.graphics.PathEffect
114
+ import androidx.compose.ui.graphics.PointMode
104
115
import androidx.compose.ui.graphics.graphicsLayer
105
116
import androidx.compose.ui.layout.ContentScale
117
+ import androidx.compose.ui.layout.boundsInParent
118
+ import androidx.compose.ui.layout.onPlaced
106
119
import androidx.compose.ui.platform.LocalDensity
107
120
import androidx.compose.ui.tooling.preview.Preview
108
121
import androidx.compose.ui.unit.Dp
109
122
import androidx.compose.ui.unit.IntSize
110
123
import androidx.compose.ui.unit.dp
124
+ import androidx.compose.ui.unit.round
111
125
import com.example.compose.snippets.R
112
126
import java.text.BreakIterator
113
127
import java.text.StringCharacterIterator
114
128
import kotlinx.coroutines.delay
129
+ import kotlinx.coroutines.isActive
115
130
116
131
/*
117
132
* Copyright 2023 The Android Open Source Project
@@ -709,6 +724,101 @@ private fun AnimationSpecKeyframe() {
709
724
// [END android_compose_animations_spec_keyframe]
710
725
}
711
726
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
+
712
822
@Composable
713
823
private fun AnimationSpecRepeatable () {
714
824
// [START android_compose_animations_spec_repeatable]
0 commit comments