I'm trying to achieve a smooth animation of a simple round timer. Like this, but smoother
However it just skips to targetValue immediately and that's it there's no animation at all. I'm trying to do it like this:
#Composable
private fun SampleTimer(duration: Int, modifier: Modifier = Modifier) {
var animatedPercentage by remember { mutableStateOf(1f) }
LaunchedEffect(Unit) {
animate(
initialValue = 1f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
tween(
durationMillis = duration.seconds.inWholeMilliseconds.toInt(),
easing = LinearEasing,
),
),
) { value, _ ->
animatedPercentage = value
}
}
val arcColor = MaterialTheme.colors.primaryVariant
Canvas(
modifier = modifier,
) {
drawArc(
color = arcColor,
useCenter = true,
startAngle = -90f,
sweepAngle = -360f * animatedPercentage,
)
}
}
Why does this happen, what am I missing here?
You can use an Animatable state. The angle will animate from 0–360°.
Something like:
val angle = remember {
Animatable(0f)
}
LaunchedEffect(angle) {
launch {
angle.animateTo(360f, animationSpec =
infiniteRepeatable(
tween(
durationMillis = 5000,
easing = LinearEasing,
),
)
)
}
}
val arcColor = Red
Canvas(
modifier = Modifier.size(100.dp),
) {
drawArc(
color = arcColor,
useCenter = true,
startAngle = -90f,
sweepAngle = -angle.value,
)
}
The problem was that the animations were turned off in developer settings on my device, and I forgot that
Related
I have an application with 2 screens: (ScreenA and ScreenB)
I created a translation animation that launches at the start of the app on screenA.
val horizontalBias = remember { Animatable(0f) }
val alignment by derivedStateOf { BiasAlignment.Horizontal(horizontalBias.value) }
LaunchedEffect(key1 = 1) {
horizontalBias.animateTo(
targetValue = -1f,
animationSpec = tween(
durationMillis = durationMillis,
delayMillis = delayMillis
)
)
}
Box(modifier = modifier)
{
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = alignment)
{
...
}
}
I would like this animation to play when launching the application but not when navigating up from screenB
I would also like the animation not play when the orientation of device change
Edit
I don't know if this is a good way but I found a solution that works:
var horizontalBias by rememberSaveable {
mutableStateOf(0f)
}
val horizontalAnimatable = remember { Animatable(horizontalBias) }
val alignment by derivedStateOf { BiasAlignment.Horizontal(horizontalAnimatable.value) }
LaunchedEffect(true) {
if (horizontalBias > -1f) {
horizontalAnimatable.animateTo(
targetValue = -1f,
animationSpec = tween(
durationMillis = durationMillis,
delayMillis = delayMillis
)
)
horizontalBias = -1f
}
}
For animation to run once you can keep a value in VieModel and
LaunchedEffect(key1 = viewModel.isAnimationRun) {
if(!vieModel.isAnimationRun)
horizontalBias.animateTo(
targetValue = -1f,
animationSpec = tween(
durationMillis = durationMillis,
delayMillis = delayMillis
)
)
viewModel.animationRun = true
}
}
Below code is in java. We need to do same thing using jetpack compose. please help us to achieve this.
val flashAnimatorSet = AnimatorSet()
val layer = ObjectAnimator.ofInt(
image,
alpha,
255,
77
)
layer.repeatMode = ValueAnimator.REVERSE
layer.repeatCount = ObjectAnimator.INFINITE
flashAnimatorSet.play(lightLayer)
flashAnimatorSet.duration = 100L
flashAnimatorSet.interpolator = AccelerateDecelerateInterpolator()
return flashAnimatorSet
You can use a InfiniteRepeatableSpec to repeat the provided animation infinite amount of times.
Something like:
val infiniteTransition = rememberInfiniteTransition()
val alpha by infiniteTransition.animateFloat(
initialValue = 1F,
targetValue = 0.28F,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = FastOutSlowInEasing
),
repeatMode = RepeatMode.Reverse
)
)
Then apply it to your Composable, for example:
Image(
painterResource(id = R.drawable.xxx),
modifier = Modifier
.graphicsLayer {
this.alpha = alpha
},
contentDescription = "contentDescription"
)
I am trying to make shaking animation of shape in Jetpack Compose. I want to use this animation to show error when user enters invalid Pin code. But all I can find is slide in, slide out animations and some scale animations. Any ideas how I can accomplish this?
Update:
After #Thracian answer. I used code as below, shaking my items horizontally:
fun Modifier.shake(enabled: Boolean, onAnimationFinish: () -> Unit) = composed(
factory = {
val distance by animateFloatAsState(
targetValue = if (enabled) 15f else 0f,
animationSpec = repeatable(
iterations = 8,
animation = tween(durationMillis = 50, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
),
finishedListener = { onAnimationFinish.invoke() }
)
Modifier.graphicsLayer {
translationX = if (enabled) distance else 0f
}
},
inspectorInfo = debugInspectorInfo {
name = "shake"
properties["enabled"] = enabled
}
)
Gif is slower than actual animation unfortunately but it gives an idea of outcome.
This can be done in many ways. You should change scaleX or scaleY or both in short time duration to have a shake effect. If you wish to have rotation change rotationZ of Modifier.graphicsLayer either
#Composable
private fun ShakeAnimationSamples() {
Column(modifier = Modifier
.fillMaxSize()
.padding(10.dp)) {
var enabled by remember {
mutableStateOf(false)
}
val scale by animateFloatAsState(
targetValue = if (enabled) .9f else 1f,
animationSpec = repeatable(
iterations = 5,
animation = tween(durationMillis = 50, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
),
finishedListener = {
enabled = false
}
)
val infiniteTransition = rememberInfiniteTransition()
val scaleInfinite by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = .85f,
animationSpec = infiniteRepeatable(
animation = tween(30, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
val rotation by infiniteTransition.animateFloat(
initialValue = -10f,
targetValue = 10f,
animationSpec = infiniteRepeatable(
animation = tween(30, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
Icon(
imageVector = Icons.Default.NotificationsActive,
contentDescription = null,
tint = Color.White,
modifier = Modifier
.graphicsLayer {
scaleX = if (enabled) scale else 1f
scaleY = if (enabled) scale else 1f
}
.background(Color.Red, CircleShape)
.size(50.dp)
.padding(10.dp)
)
Icon(
imageVector = Icons.Default.NotificationsActive,
contentDescription = null,
tint = Color.White,
modifier = Modifier
.graphicsLayer {
scaleX = scaleInfinite
scaleY = scaleInfinite
rotationZ = rotation
}
.background(Color.Red, CircleShape)
.size(50.dp)
.padding(10.dp)
)
Button(onClick = { enabled = !enabled }) {
Text("Animation enabled: $enabled")
}
}
}
Also you can do it as a Modifier either
fun Modifier.shake(enabled: Boolean) = composed(
factory = {
val scale by animateFloatAsState(
targetValue = if (enabled) .9f else 1f,
animationSpec = repeatable(
iterations = 5,
animation = tween(durationMillis = 50, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
Modifier.graphicsLayer {
scaleX = if (enabled) scale else 1f
scaleY = if (enabled) scale else 1f
}
},
inspectorInfo = debugInspectorInfo {
name = "shake"
properties["enabled"] = enabled
}
)
Usage
Icon(
imageVector = Icons.Default.NotificationsActive,
contentDescription = null,
tint = Color.White,
modifier = Modifier
.shake(enabled)
.background(Color.Red, CircleShape)
.size(50.dp)
.padding(10.dp)
)
How to do my image to rotate infinitely?
This is my code but animation does not work
val angle: Float by animateFloatAsState(
targetValue = 360F,
animationSpec = infiniteRepeatable(
tween(2000))
)
Image(
painter = painterResource(R.drawable.sonar_scanner),
"image",
Modifier
.fillMaxSize()
.rotate(angle),
contentScale = ContentScale.Fit
)
You can use the InfiniteTransition using rememberInfiniteTransition.
Something like
val infiniteTransition = rememberInfiniteTransition()
val angle by infiniteTransition.animateFloat(
initialValue = 0F,
targetValue = 360F,
animationSpec = infiniteRepeatable(
animation = tween(2000, easing = LinearEasing)
)
)
Just a note.
Instead of using
Modifier.rotate(angle)
You can use
Modifier
.graphicsLayer {
rotationZ = angle
}
As you can check in the doc:
Prefer this version when you have layer properties backed by a androidx.compose.runtime.State or an animated value as reading a state inside block will only cause the layer properties update without triggering recomposition and relayout.
You should use the pre-defined API for infinite animations for such use cases in my opinion.
val infiniteTransition = rememberInfiniteTransition()
val angle by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = 1000
}
)
)
In the past (before alpha11), I can animate a value from 0 to 1 upon triggering the composable function as below, where I can set initialValue and also have onActive with aniumateTo.
val animatedProgress = animatedFloat(0f)
onActive {
animatedProgress.animateTo(
targetValue = 1f,
anim = infiniteRepeatable(
animation =
tween(durationMillis = 2000, easing = LinearEasing),
)
)
}
val t = animatedProgress.value
However, now in alpha13, I cannot find a way to set initialValue, or animateTo. The onActive is also now deprecated.
I coded as below
val floatAnimation = animateFloatAsState(
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 2000, easing = LinearEasing),
)
)
How can I...
Set initial value of 0
Starting the animation (without needing a state boolean to kick it off)
Animate from 0 to 1 repeatedly
You can use the Animatable API and the LaunchedEffect composable. You don't need a boolean to start the animation.
Something like:
val animatedAlpha = remember { Animatable(0f) }
Box(
Modifier
.background(color = (Color.Blue.copy(alpha = animatedAlpha.value)))
.size(100.dp,100.dp)
)
LaunchedEffect(animatedAlpha) {
animatedAlpha.animateTo(1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 2000, easing = LinearEasing)
))
}
Looks like I have to use a boolean to change the state and use LaunchEffect to start and change the state as below.
var start by remember{ mutableStateOf(false) }
val floatAnimation = animateFloatAsState(
targetValue = if (start) 1f else 0f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 2000, easing = LinearEasing),
)
)
LaunchedEffect(true) {
start = true
}
val t = floatAnimation.value
Not sure if this is the best way to code around it.