Colton Idle
07/30/2021, 12:51 AMTash
07/30/2021, 1:22 AMColton Idle
07/30/2021, 2:35 AM@Composable
fun InfiniteAnimationDemo() {
val alpha = remember { mutableStateOf(0f) }
LaunchedEffect(Unit) {
animate(
initialValue = 20f,
targetValue = -20f,
animationSpec =
infiniteRepeatable(animation = tween(1000), repeatMode = RepeatMode.Reverse)) {
value,
_ ->
alpha.value = value
}
}
Box(Modifier.size(200.dp)) {
Icon(
Icons.Filled.Favorite,
contentDescription = null,
modifier =
Modifier.align(Alignment.Center)
.graphicsLayer(translationY = alpha.value),
tint = Color.Red)
}
}
Tash
07/30/2021, 3:34 AMrememberInfiniteTransition()
and a Dp.VectorConverter
you can use an animation like keyframes
to get more control:
@Composable
fun InfiniteTransitionDemo() {
val infiniteTransition = rememberInfiniteTransition()
val offsetY: Dp by infiniteTransition.animateValue(
initialValue = 0.dp,
targetValue = 100.dp,
typeConverter = Dp.VectorConverter,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = 500
100.dp.unaryMinus() at 200 with FastOutLinearInEasing // etc
80.dp at 300 with FastOutSlowInEasing // etc
}
)
)
Box(Modifier.fillMaxSize()) {
Icon(
Icons.Filled.Favorite,
null,
Modifier
.align(Alignment.Center)
.offset(y = offsetY),
)
}
}
Doris Liu
07/30/2021, 4:04 AMkeyframes
with a few nicely crafted entries would allow the duck to bob more lively... π
The slow-down that you see @Colton Idle is due to the default easing. Here's a good overview of how different easing curves affect the overall movement: https://medium.com/mobile-app-development-publication/android-jetpack-compose-animation-spec-made-easy-6e7990aef203Colton Idle
07/30/2021, 4:53 AMhave you tried animating the Y offset instead?I thought that's what I was doing here:
.graphicsLayer(translationY =...
Guess not. π
@Doris Liu oh! Easing. I thought it was due to a default interpolator or something. I tried guessing some interpolator apis but nothing autofilled so I gave up. Easing makes sense though.
I have to admit that the code snippet Tash posted is going over my head
This piece makes for a fairly frantic animation.
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = 500
100.dp.unaryMinus() at 200 with FastOutLinearInEasing // etc
80.dp at 300 with FastOutSlowInEasing // etc
}
)
@Doris Liu would you recommend the first snippet I posted with a different easing set, or would you reccomend trying to learn the keyframes api and moving forward with that?Doris Liu
07/30/2021, 5:20 AMFastOutLinearInEasing
might work better. But I would recommend playing with keyframes
. I'm imagining a smaller bounce after the initial bounce. So it'd be alternating between different value ranges. Something like this: π
val translationY: Float by infiniteTransition.animateFloat(
initialValue = -20f,
targetValue = -20f,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = 2000
20f at 600 with LinearOutSlowInEasing
-10f at 1100
10f at 1500 with FastOutSlowInEasing // etc
}
)
)
You could also rotate the duck a little as it bobs with another animation in the same infinite transition. πColton Idle
07/30/2021, 3:06 PMDoris Liu
07/30/2021, 6:07 PMinfiniteRepeatable
is the animationSpec
that will be needed in both cases. But between InfiniteTransition
and coroutine-based animate
, it's more of the personal preference when it comes to infinite animations. There's no significant performance difference between the two. InfiniteTransition
is a convenient API that internally creates a coroutine and run animations in it, which is not that different than what the first snippet is doing. π
With that said, I'd use InfiniteTransition
in this case since designer might decide to add more animations in the future.Colton Idle
07/30/2021, 6:34 PM@Composable
fun InfiniteTransitionDemo() {
val infiniteTransition = rememberInfiniteTransition()
val offsetY: Dp by infiniteTransition.animateValue(
initialValue = 0.dp,
targetValue = 20.dp,
typeConverter = Dp.VectorConverter,
animationSpec =
infiniteRepeatable(
animation =
keyframes {
val duration = 2000
durationMillis = duration
0.dp at 0 with FastOutSlowInEasing
20.dp at duration / 2 with FastOutSlowInEasing
0.dp at duration with FastOutSlowInEasing
}))
Box(Modifier.fillMaxSize()) {
Icon(
Icons.Filled.Favorite,
null,
Modifier.align(Alignment.Center).offset(y = offsetY),
)
}
}
I still have two questions if anyone is intrigued:
1. I'm still a bit confused with the significance of the dp values. I want the duck to bob starting at 0, and go up 20dp and then -20dp. So 0 is it's starting point.
initialValue = 0.dp,
targetValue = 20.dp,
and
0.dp at 0 with FastOutSlowInEasing
20.dp at duration / 2 with FastOutSlowInEasing
0.dp at duration with FastOutSlowInEasing
2. Still not happy with the easing/animation/interpolation. I want it to be springy but the animation not to speed up in the middle. I guess almost like I just want a constant speed but a little bounce.Doris Liu
07/30/2021, 6:51 PMColton Idle
07/30/2021, 6:56 PMDoris Liu
07/30/2021, 6:59 PM...
modifier =
Modifier.align(Alignment.Center)
.graphicsLayer { translationY = /*animated translation value goes here*/ },
Colton Idle
07/30/2021, 7:14 PMColton Idle
07/30/2021, 7:36 PM0.dp at 0 with CubicBezierEasing(0.68F, -0.55F, 0.265F, 1.55F)
20.dp at duration / 2 with CubicBezierEasing(0.68F, -0.55F, 0.265F, 1.55F)
0.dp at duration with CubicBezierEasing(0.68F, -0.55F, 0.265F, 1.55F)
because I want to start at 0, go to 20, then back down to 0. That's 3 keyframes but actually two transitions/events, so why do I have to define 3 easings? I think that's whats screwing me over. Shouldn't I just defined two Easings. 1 for each actual event.Tash
07/30/2021, 8:29 PM20.dp at duration / 2 with CubicBezierEasing(0.68F, -0.55F, 0.265F, 1.55F)
0.dp at duration with CubicBezierEasing(0.68F, -0.55F, 0.265F, 1.55F)
does that change anything?Colton Idle
07/30/2021, 8:49 PMColton Idle
07/30/2021, 8:51 PMDoris Liu
07/30/2021, 9:00 PMwith
defines the easing curve for the interval starting at the timestamp you provided, til the next keyframe entry. This means .. at duration with ..
isn't necessary.
BTW, it's very straightforward to impl Easing
with an OvershootInterpolator πColton Idle
07/30/2021, 9:36 PMDoris Liu
07/30/2021, 9:40 PMColton Idle
07/31/2021, 12:21 AM@Composable
fun InfiniteTransitionDemo() {
val infiniteTransition = rememberInfiniteTransition()
val offsetY: Dp by infiniteTransition.animateValue(
initialValue = 0.dp,
targetValue = 20.dp,
typeConverter = Dp.VectorConverter,
animationSpec =
infiniteRepeatable(
repeatMode = RepeatMode.Reverse,
animation =
tween(
durationMillis = 2000,
easing = { OvershootInterpolator().getInterpolation(it) })))
Box(Modifier.fillMaxSize()) {
Icon(
Icons.Filled.Favorite,
null,
Modifier.align(Alignment.Center).offset(y = offsetY),
)
}
}
Doris Liu
07/31/2021, 1:58 AMBounceInterpolator
also, see the orange curve here: http://dev.antoine-merle.com/blog/2013/04/12/making-a-bounce-animation-for-your-sliding-menu/
It might also help to add a small (~20ms) delay to the tween, so that after it animates back to the beginning it pauses a little.Colton Idle
07/31/2021, 2:22 AM@Composable
fun InfiniteTransitionDemo() {
val infiniteTransition = rememberInfiniteTransition()
val offsetY: Dp by infiniteTransition.animateValue(
initialValue = 0.dp,
targetValue = 20.dp,
typeConverter = Dp.VectorConverter,
animationSpec =
infiniteRepeatable(
repeatMode = RepeatMode.Reverse,
animation =
tween(
delayMillis = 20,
durationMillis = 2000,
easing = { BounceInterpolator().getInterpolation(it) })))
Box(Modifier.fillMaxSize()) {
Icon(
Icons.Filled.Favorite,
null,
Modifier.align(Alignment.Center).offset(y = offsetY),
)
}
}
Maybe I just need an example of what I want it to look like, because this looks far off from what my design team wants and I'm failing horribly at making it work. lolColton Idle
07/31/2021, 2:28 AM@Composable
fun InfiniteTransitionDemo() {
val infiniteTransition = rememberInfiniteTransition()
val offsetY: Dp by infiniteTransition.animateValue(
initialValue = 0.dp,
targetValue = 20.dp,
typeConverter = Dp.VectorConverter,
animationSpec =
infiniteRepeatable(
repeatMode = RepeatMode.Reverse,
animation =
tween(
durationMillis = 2000,
easing = { LinearInterpolator().getInterpolation(it) })))
Box(Modifier.fillMaxSize()) {
Icon(
Icons.Filled.Favorite,
null,
Modifier.align(Alignment.Center).offset(y = offsetY),
)
}
}
Here is something that works almost exactly like I want it except that when it reaches the high point and the low point I want it to overshoot a little bit/act springy. But I want to keep the linear ease during the duration. I just want it to feel more natural and less robotic. Anyone have any last suggestions?Doris Liu
07/31/2021, 3:09 AMColton Idle
07/31/2021, 3:17 AMColton Idle
07/31/2021, 3:23 AMColton Idle
07/31/2021, 5:02 AMChris Sinco [G]
07/31/2021, 5:02 AMColton Idle
07/31/2021, 5:03 AMChris Sinco [G]
07/31/2021, 5:03 AMColton Idle
07/31/2021, 5:05 AMChris Sinco [G]
07/31/2021, 5:08 AMColton Idle
07/31/2021, 5:24 AMColton Idle
07/31/2021, 5:24 AM<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="<http://schemas.android.com/apk/res/android>"
xmlns:motion="<http://schemas.android.com/apk/res-auto>">
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="2000"
motion:motionInterpolator="linear">
<KeyFrameSet>
<KeyTimeCycle
android:translationY="5dp"
motion:framePosition="1"
motion:motionTarget="@+id/textView"
motion:waveOffset="0.1"
motion:wavePeriod="0.5"
motion:waveShape="cos" />
</KeyFrameSet>
</Transition>
</MotionScene>
Colton Idle
07/31/2021, 5:25 AMColton Idle
07/31/2021, 5:26 AMDoris Liu
07/31/2021, 6:41 AMColton Idle
07/31/2021, 12:57 PMColton Idle
07/31/2021, 1:12 PMeasing = { CycleInterpolator(3f).getInterpolation(it) })))
this is soooo close. It just jumps "randomly" (probably not randomly, its probably doing exactly what I told it to)Colton Idle
07/31/2021, 1:21 PMinfiniteRepeatable(
repeatMode = RepeatMode.Restart,
animation =
tween(
durationMillis = 5000,
easing = { CycleInterpolator(3f).getInterpolation(it) })))
Thanks Doris and Chris for steering me in the right direction. I still gotta admit that I don't understand why the other techniques didn't work. Oh π³Doris Liu
07/31/2021, 4:39 PMrepeatables
in the animation inspector tool that Chris mentioned. πStylianos Gakis
01/13/2022, 7:23 PMDoris Liu
01/13/2022, 7:30 PMStylianos Gakis
01/13/2022, 7:55 PM