EmergeTools logo
🙏 Supported by Emerge Tools
The platform for all your mobile performance needs

Animated Loader Component

Common dots loading animations implemented using Jetpack Compose. Here's a demo of what these look like in action

Animated Loader Component demo

import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
val dotSize = 24.dp // made it bigger for demo
val delayUnit = 300 // you can change delay to change animation speed
@Composable
fun DotsPulsing() {
@Composable
fun Dot(
scale: Float
) = Spacer(
Modifier
.size(dotSize)
.scale(scale)
.background(
color = MaterialTheme.colors.primary,
shape = CircleShape
)
)
val infiniteTransition = rememberInfiniteTransition()
@Composable
fun animateScaleWithDelay(delay: Int) = infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = delayUnit * 4
0f at delay with LinearEasing
1f at delay + delayUnit with LinearEasing
0f at delay + delayUnit * 2
}
)
)
val scale1 by animateScaleWithDelay(0)
val scale2 by animateScaleWithDelay(delayUnit)
val scale3 by animateScaleWithDelay(delayUnit * 2)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
val spaceSize = 2.dp
Dot(scale1)
Spacer(Modifier.width(spaceSize))
Dot(scale2)
Spacer(Modifier.width(spaceSize))
Dot(scale3)
}
}
@Composable
fun DotsElastic() {
val minScale = 0.6f
@Composable
fun Dot(
scale: Float
) = Spacer(
Modifier
.size(dotSize)
.scale(scaleX = minScale, scaleY = scale)
.background(
color = MaterialTheme.colors.primary,
shape = CircleShape
)
)
val infiniteTransition = rememberInfiniteTransition()
@Composable
fun animateScaleWithDelay(delay: Int) = infiniteTransition.animateFloat(
initialValue = minScale,
targetValue = minScale,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = delayUnit * 4
minScale at delay with LinearEasing
1f at delay + delayUnit with LinearEasing
minScale at delay + delayUnit * 2
}
)
)
val scale1 by animateScaleWithDelay(0)
val scale2 by animateScaleWithDelay(delayUnit)
val scale3 by animateScaleWithDelay(delayUnit * 2)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
val spaceSize = 2.dp
Dot(scale1)
Spacer(Modifier.width(spaceSize))
Dot(scale2)
Spacer(Modifier.width(spaceSize))
Dot(scale3)
}
}
@Composable
fun DotsFlashing() {
val minAlpha = 0.1f
@Composable
fun Dot(
alpha: Float
) = Spacer(
Modifier
.size(dotSize)
.alpha(alpha)
.background(
color = MaterialTheme.colors.primary,
shape = CircleShape
)
)
val infiniteTransition = rememberInfiniteTransition()
@Composable
fun animateAlphaWithDelay(delay: Int) = infiniteTransition.animateFloat(
initialValue = minAlpha,
targetValue = minAlpha,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = delayUnit * 4
minAlpha at delay with LinearEasing
1f at delay + delayUnit with LinearEasing
minAlpha at delay + delayUnit * 2
}
)
)
val alpha1 by animateAlphaWithDelay(0)
val alpha2 by animateAlphaWithDelay(delayUnit)
val alpha3 by animateAlphaWithDelay(delayUnit * 2)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
val spaceSize = 2.dp
Dot(alpha1)
Spacer(Modifier.width(spaceSize))
Dot(alpha2)
Spacer(Modifier.width(spaceSize))
Dot(alpha3)
}
}
@Composable
fun DotsTyping() {
val maxOffset = 10f
@Composable
fun Dot(
offset: Float
) = Spacer(
Modifier
.size(dotSize)
.offset(y = -offset.dp)
.background(
color = MaterialTheme.colors.primary,
shape = CircleShape
)
)
val infiniteTransition = rememberInfiniteTransition()
@Composable
fun animateOffsetWithDelay(delay: Int) = infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = delayUnit * 4
0f at delay with LinearEasing
maxOffset at delay + delayUnit with LinearEasing
0f at delay + delayUnit * 2
}
)
)
val offset1 by animateOffsetWithDelay(0)
val offset2 by animateOffsetWithDelay(delayUnit)
val offset3 by animateOffsetWithDelay(delayUnit * 2)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(top = maxOffset.dp)
) {
val spaceSize = 2.dp
Dot(offset1)
Spacer(Modifier.width(spaceSize))
Dot(offset2)
Spacer(Modifier.width(spaceSize))
Dot(offset3)
}
}
@Composable
fun DotsCollision() {
val maxOffset = 30f
val delayUnit = 500 // it's better to use longer delay for this animation
@Composable
fun Dot(
offset: Float
) = Spacer(
Modifier
.size(dotSize)
.offset(x = offset.dp)
.background(
color = MaterialTheme.colors.primary,
shape = CircleShape
)
)
val infiniteTransition = rememberInfiniteTransition()
val offsetLeft by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = delayUnit * 3
0f at 0 with LinearEasing
-maxOffset at delayUnit / 2 with LinearEasing
0f at delayUnit
}
)
)
val offsetRight by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = delayUnit * 3
0f at delayUnit with LinearEasing
maxOffset at delayUnit + delayUnit / 2 with LinearEasing
0f at delayUnit * 2
}
)
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(horizontal = maxOffset.dp)
) {
val spaceSize = 2.dp
Dot(offsetLeft)
Spacer(Modifier.width(spaceSize))
Dot(0f)
Spacer(Modifier.width(spaceSize))
Dot(offsetRight)
}
}
@Preview(showBackground = true)
@Composable
fun DotsPreview() = MaterialTheme {
Column(modifier = Modifier.padding(4.dp)) {
val spaceSize = 16.dp
Text(
text = "Dots pulsing",
style = MaterialTheme.typography.h5
)
DotsPulsing()
Spacer(Modifier.height(spaceSize))
Text(
text = "Dots elastic",
style = MaterialTheme.typography.h5
)
DotsElastic()
Spacer(Modifier.height(spaceSize))
Text(
text = "Dots flashing",
style = MaterialTheme.typography.h5
)
DotsFlashing()
Spacer(Modifier.height(spaceSize))
Text(
text = "Dots typing",
style = MaterialTheme.typography.h5
)
DotsTyping()
Spacer(Modifier.height(spaceSize))
Text(
text = "Dots collision",
style = MaterialTheme.typography.h5
)
DotsCollision()
}
}
view raw DotsLoaders.kt hosted with ❤ by GitHub

Have a project you'd like to submit? Fill this form, will ya!

If you like this snippet, you might also like:

Maker OS is an all-in-one productivity system for developers

I built Maker OS to track, manage & organize my life. Now you can do it too!