Created
March 18, 2021 23:15
-
-
Save EugeneTheDev/a27664cb7e7899f964348b05883cbccd to your computer and use it in GitHub Desktop.
Dots loading animations with Jetpack Compose
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
thanks!!!!
I am looking for some good animation examples. This is very helpful
Mike
Cotuit, USA
awesome
Added support for modifying number of dots & it's color
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.State
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.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
const val numberOfDots = 7
val dotSize = 10.dp
val dotColor: Color = Color.Blue
const val delayUnit = 200
const val duration = numberOfDots * delayUnit
val spaceBetween = 2.dp
@Composable
fun DotsPulsing() {
@Composable
fun Dot(scale: Float) {
Spacer(
Modifier
.size(dotSize)
.scale(scale)
.background(
color = dotColor,
shape = CircleShape
)
)
}
val infiniteTransition = rememberInfiniteTransition()
@Composable
fun animateScaleWithDelay(delay: Int) = infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 0f,
animationSpec = infiniteRepeatable(animation = keyframes {
durationMillis = delayUnit * numberOfDots
0f at delay with LinearEasing
1f at delay + delayUnit with LinearEasing
0f at delay + duration
})
)
val scales = arrayListOf<State<Float>>()
for (i in 0 until numberOfDots) {
scales.add(animateScaleWithDelay(delay = i * delayUnit))
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
scales.forEach {
Dot(it.value)
Spacer(Modifier.width(spaceBetween))
}
}
}
@Composable
fun DotsElastic() {
val minScale = 0.6f
@Composable
fun Dot(scale: Float) {
Spacer(
Modifier
.size(dotSize)
.scale(scaleX = minScale, scaleY = scale)
.background(
color = dotColor,
shape = CircleShape
)
)
}
val infiniteTransition = rememberInfiniteTransition()
@Composable
fun animateScaleWithDelay(delay: Int) = infiniteTransition.animateFloat(
initialValue = minScale,
targetValue = minScale,
animationSpec = infiniteRepeatable(animation = keyframes {
durationMillis = duration
minScale at delay with LinearEasing
1f at delay + delayUnit with LinearEasing
minScale at delay + duration
})
)
val scales = arrayListOf<State<Float>>()
for (i in 0 until numberOfDots) {
scales.add(animateScaleWithDelay(delay = i * delayUnit))
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
scales.forEach {
Dot(it.value)
Spacer(Modifier.width(spaceBetween))
}
}
}
@Composable
fun DotsFlashing() {
val minAlpha = 0.1f
@Composable
fun Dot(alpha: Float) = Spacer(
Modifier
.size(dotSize)
.alpha(alpha)
.background(
color = dotColor, shape = CircleShape
)
)
val infiniteTransition = rememberInfiniteTransition()
@Composable
fun animateAlphaWithDelay(delay: Int) = infiniteTransition.animateFloat(
initialValue = minAlpha,
targetValue = minAlpha,
animationSpec = infiniteRepeatable(animation = keyframes {
durationMillis = duration
minAlpha at delay with LinearEasing
1f at delay + delayUnit with LinearEasing
minAlpha at delay + duration
})
)
val alphas = arrayListOf<State<Float>>()
for (i in 0 until numberOfDots) {
alphas.add(animateAlphaWithDelay(delay = i * delayUnit))
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
alphas.forEach {
Dot(it.value)
Spacer(Modifier.width(spaceBetween))
}
}
}
@Composable
fun DotsTyping() {
val maxOffset = (numberOfDots * 2).toFloat()
@Composable
fun Dot(offset: Float) {
Spacer(
Modifier
.size(dotSize)
.offset(y = -offset.dp)
.background(
color = dotColor,
shape = CircleShape
)
)
}
val infiniteTransition = rememberInfiniteTransition()
@Composable
fun animateOffsetWithDelay(delay: Int) = infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 0f,
animationSpec = infiniteRepeatable(animation = keyframes {
durationMillis = duration
0f at delay with LinearEasing
maxOffset at delay + delayUnit with LinearEasing
0f at delay + (duration/2)
})
)
val offsets = arrayListOf<State<Float>>()
for (i in 0 until numberOfDots) {
offsets.add(animateOffsetWithDelay(delay = i * delayUnit))
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(top = maxOffset.dp)
) {
offsets.forEach {
Dot(it.value)
Spacer(Modifier.width(spaceBetween))
}
}
}
@Composable
fun DotsCollision() {
val maxOffset = 30f
val delayUnit = 500
@Composable
fun Dot(offset: Float) {
Spacer(
Modifier
.size(dotSize)
.offset(x = offset.dp)
.background(
color = dotColor,
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)
) {
Dot(offsetLeft)
Spacer(Modifier.width(spaceBetween))
for (i in 0 until numberOfDots-2) {
Dot(0f)
Spacer(Modifier.width(spaceBetween))
}
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()
}
}
check this out:
https://github.com/razaghimahdi/Compose-Loading-Dots
@EugeneTheDev Thank you for this gist. It saved me a lot of time for pulsating dots in Compose.
Great job!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How it looks (gif is jaggy, try to run it)