Animated Background with Pause
Introduction & Purpose
I sometimes like to add animated background to my apps to make them a little more interesting to work on, like in this very simple example above.
Unfortunately, the animation is not free. It takes up CPU time, can waste battery, and also heat up physical devices. We definitely want to avoid that. It's also great to have a way to stop animations for people who are sensitive to motion.
We could just remove the animated background, but this makes things a little boring if I want to show someone what I have been working on. We can fix this!
I'll keep this post quick. Full code listing linked at the end.
Animated Background
Let's start with a basic animated background. This is our gradient and this will be reused with our final example.
@Composable
private fun DrawGradient(
offset: Float,
modifier: Modifier = Modifier
) {
Box(modifier.fillMaxSize().drawBehind {
val shift = offset * 3000f
drawRect(
brush = Brush.linearGradient(
colors = listOf(
Color.Magenta,
Color.Cyan,
Color.Yellow,
Color.Magenta
),
start = Offset(shift - 1500f, 0f),
end = Offset(shift + 1500f, 0f)
)
)
})
}
@Composable
private fun BasicAnimatedGradientBackground() {
val offset by rememberInfiniteTransition(label = "")
.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
tween(3000, easing = LinearEasing)
),
label = ""
)
DrawGradient(offset)
}
Improved Animated Background
Now we can improve that by accepting a modifier and pause. We will use withFrameMillis to sync the drawing to our device frame rate. We will also add frames - a counter so we can check how much work is being done.
Important: Be sure to remove frames and the println if you want to use this as that also wastes resources.
@Composable
private fun AnimatedGradientBackground(
modifier: Modifier = Modifier,
paused: Boolean = false
) {
var frames by remember { mutableIntStateOf(0) }
var offset by remember {
mutableFloatStateOf(0f)
}
LaunchedEffect(paused) {
while (!paused) {
withFrameMillis { frameMs ->
frames++
println("frame: $frames")
offset = (frameMs % 3000L) / 3000f
}
}
}
DrawGradient(offset, modifier)
}
Looks good, but that won't work yet!
Controlling the Animation
We need to add something to control the animation. We will use a simple modifier. For this, I decided to use a drag action with detectDragGestures and onDragStart. We don't need onDragEnd or onDragCancel for this.
private fun Modifier.pausableByDrag(
onPauseChanged: () -> Unit
): Modifier = this.pointerInput(Unit) {
detectDragGestures(
onDragStart = { onPauseChanged() },
onDrag = { change, _ ->
change.consume()
}
)
}
And we call the background using that modifier while tracking the pause state:
@Composable
private fun AnimatedBackgroundDragPause(
) {
var paused by remember {
mutableStateOf(false)
}
AnimatedGradientBackground(
modifier = Modifier.pausableByDrag {
paused = !paused
},
paused = paused
)
}
Simply drag in any direction to pause/unpause the animation.
When to Use?
Great, so now we have a background that pauses... Use this when you have the application open for an extended period of time! This will save power, generate less heat, and help with device longevity! Think of this as a way to protect the environment and your money.
If you are showing something to someone sensitive to motion, a quick drag will solve that problem!
Suggestion
If you will be working a lot with one screen, default it to paused and use the drag to show the animation only when you want to see it.
Summary
Animated backgrounds can look great (far better than this simple example), but they do waste resources. Adding a pause is a simple way to keep the background and save resources when you need to.


Comments
Post a Comment