Skip to content

Commit 0a00af3

Browse files
committed
Add example of keyframesWithSplines
1 parent 5bf449b commit 0a00af3

File tree

1 file changed

+112
-0
lines changed

1 file changed

+112
-0
lines changed

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

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import androidx.compose.animation.animateContentSize
2929
import androidx.compose.animation.core.AnimationVector1D
3030
import androidx.compose.animation.core.AnimationVector2D
3131
import androidx.compose.animation.core.Easing
32+
import androidx.compose.animation.core.ExperimentalAnimationSpecApi
3233
import androidx.compose.animation.core.ExperimentalTransitionApi
3334
import androidx.compose.animation.core.FastOutLinearInEasing
3435
import androidx.compose.animation.core.FastOutSlowInEasing
@@ -43,11 +44,13 @@ import androidx.compose.animation.core.TwoWayConverter
4344
import androidx.compose.animation.core.VectorConverter
4445
import androidx.compose.animation.core.animateDp
4546
import androidx.compose.animation.core.animateFloatAsState
47+
import androidx.compose.animation.core.animateOffsetAsState
4648
import androidx.compose.animation.core.animateRect
4749
import androidx.compose.animation.core.animateValueAsState
4850
import androidx.compose.animation.core.createChildTransition
4951
import androidx.compose.animation.core.infiniteRepeatable
5052
import androidx.compose.animation.core.keyframes
53+
import androidx.compose.animation.core.keyframesWithSpline
5154
import androidx.compose.animation.core.rememberInfiniteTransition
5255
import androidx.compose.animation.core.rememberTransition
5356
import androidx.compose.animation.core.repeatable
@@ -71,11 +74,13 @@ import androidx.compose.foundation.Image
7174
import androidx.compose.foundation.background
7275
import androidx.compose.foundation.clickable
7376
import androidx.compose.foundation.layout.Box
77+
import androidx.compose.foundation.layout.BoxWithConstraints
7478
import androidx.compose.foundation.layout.Column
7579
import androidx.compose.foundation.layout.Row
7680
import androidx.compose.foundation.layout.fillMaxSize
7781
import androidx.compose.foundation.layout.fillMaxWidth
7882
import androidx.compose.foundation.layout.height
83+
import androidx.compose.foundation.layout.offset
7984
import androidx.compose.foundation.layout.padding
8085
import androidx.compose.foundation.layout.size
8186
import androidx.compose.foundation.layout.sizeIn
@@ -93,25 +98,34 @@ import androidx.compose.runtime.State
9398
import androidx.compose.runtime.getValue
9499
import androidx.compose.runtime.mutableIntStateOf
95100
import androidx.compose.runtime.mutableLongStateOf
101+
import androidx.compose.runtime.mutableStateListOf
96102
import androidx.compose.runtime.mutableStateOf
97103
import androidx.compose.runtime.remember
98104
import androidx.compose.runtime.setValue
99105
import androidx.compose.runtime.withFrameNanos
100106
import androidx.compose.ui.Alignment
101107
import androidx.compose.ui.Modifier
108+
import androidx.compose.ui.draw.drawBehind
109+
import androidx.compose.ui.geometry.Offset
102110
import androidx.compose.ui.geometry.Rect
103111
import androidx.compose.ui.graphics.Color
112+
import androidx.compose.ui.graphics.PathEffect
113+
import androidx.compose.ui.graphics.PointMode
104114
import androidx.compose.ui.graphics.graphicsLayer
105115
import androidx.compose.ui.layout.ContentScale
116+
import androidx.compose.ui.layout.boundsInParent
117+
import androidx.compose.ui.layout.onPlaced
106118
import androidx.compose.ui.platform.LocalDensity
107119
import androidx.compose.ui.tooling.preview.Preview
108120
import androidx.compose.ui.unit.Dp
109121
import androidx.compose.ui.unit.IntSize
110122
import androidx.compose.ui.unit.dp
123+
import androidx.compose.ui.unit.round
111124
import com.example.compose.snippets.R
112125
import java.text.BreakIterator
113126
import java.text.StringCharacterIterator
114127
import kotlinx.coroutines.delay
128+
import kotlinx.coroutines.isActive
115129

116130
/*
117131
* Copyright 2023 The Android Open Source Project
@@ -709,6 +723,104 @@ private fun AnimationSpecKeyframe() {
709723
// [END android_compose_animations_spec_keyframe]
710724
}
711725

726+
@OptIn(ExperimentalAnimationSpecApi::class)
727+
@Composable
728+
private fun AnimationSpecKeyframeWithSpline() {
729+
// [START android_compose_animation_spec_keyframes_with_spline]
730+
val offset by animateOffsetAsState(
731+
targetValue = Offset(300f, 300f),
732+
animationSpec = keyframesWithSpline {
733+
durationMillis = 6000
734+
Offset(0f, 0f) at 0
735+
Offset(150f, 200f) atFraction 0.5f
736+
Offset(0f,100f) atFraction 0.7f
737+
}
738+
)
739+
// [END android_compose_animation_spec_keyframes_with_spline]
740+
}
741+
742+
@Suppress("PrimitiveInCollection")
743+
@OptIn(ExperimentalAnimationSpecApi::class)
744+
@Preview
745+
@Composable
746+
private fun OffsetKeyframeWithSplineDemo() {
747+
val points = remember { mutableStateListOf<Offset>() }
748+
val offsetAnim = remember {
749+
androidx.compose.animation.core.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+
repeat(4) {
796+
val i = it + 1
797+
val sign = if (i % 2 == 0) 1 else -1
798+
Offset(
799+
x = maxXOff * (i.toFloat() / 5f) * sign,
800+
y = (maxYOff) * (i.toFloat() / 5f)
801+
) atFraction (0.1f * i)
802+
}
803+
804+
// Halfway point (at bottom of the screen)
805+
Offset(0f, maxYOff) atFraction 0.5f
806+
807+
// Return with mirrored movement
808+
repeat(4) {
809+
val i = it + 1
810+
val sign = if (i % 2 == 0) 1 else -1
811+
Offset(
812+
x = maxXOff * (1f - i.toFloat() / 5f) * sign,
813+
y = (maxYOff) * (1f - i.toFloat() / 5f)
814+
) atFraction ((0.1f * i) + 0.5f)
815+
}
816+
}
817+
)
818+
points.clear()
819+
}
820+
}
821+
}
822+
}
823+
712824
@Composable
713825
private fun AnimationSpecRepeatable() {
714826
// [START android_compose_animations_spec_repeatable]

0 commit comments

Comments
 (0)