In this Google Codelab (Mars Photos), while Coil is loading images, the app displays a progress wheel. The code uses this XML file as a drawable:
and uses this code to display it:
@Composable
fun LoadingScreen(modifier: Modifier = Modifier) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier.fillMaxSize()
) {
Image(
modifier = Modifier.size(200.dp),
painter = painterResource(R.drawable.loading_img),
contentDescription = stringResource(R.string.loading)
)
}
}
However, that only displays a static image. I want it to rotate (which I certainly expected as behavior). I've thought of some approaches, none of which I like much:
Is there a simpler way of accomplishing this task? Is there a way to rotate the image itself programmatically such as applying a predefined animation mode of some sort (perhaps similar to basicMarquee)?
Here's the code for the same. This code is taken from https://github.com/SmartToolFactory/Compose-ProgressIndicator and it has many other spinning circular progress view animations.
@Composable
fun SpinningProgressIndicator(
modifier: Modifier = Modifier,
@androidx.annotation.IntRange(from = 4, to = 12) staticItemCount: Int = 12,
dynamicItemCount: Int = staticItemCount / 2,
staticItemColor: Color = StaticItemColor,
dynamicItemColor: Color = DynamicItemColor,
spinnerShape: SpinnerShape = SpinnerShape.RoundedRect,
durationMillis: Int = 1000
) {
// Number of rotating items
val animatedItemCount = dynamicItemCount.coerceIn(1, staticItemCount)
val coefficient = 360f / staticItemCount
val infiniteTransition = rememberInfiniteTransition()
val angle by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = staticItemCount.toFloat(),
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = durationMillis,
easing = LinearEasing
),
repeatMode = RepeatMode.Restart
)
)
Canvas(modifier = modifier
.progressSemantics()
.size(Size)
) {
var canvasWidth = size.width
var canvasHeight = size.height
if (canvasHeight < canvasWidth) {
canvasWidth = canvasHeight
} else {
canvasHeight = canvasWidth
}
val itemWidth = canvasWidth * .3f
val itemHeight = canvasHeight / staticItemCount
val cornerRadius = itemWidth.coerceAtMost(itemHeight) / 2
val horizontalOffset = (size.width - size.height).coerceAtLeast(0f) / 2
val verticalOffset = (size.height - size.width).coerceAtLeast(0f) / 2
val topLeftOffset = Offset(
x = horizontalOffset + canvasWidth - itemWidth,
y = verticalOffset + (canvasHeight - itemHeight) / 2
)
val size = Size(itemWidth, itemHeight)
// Stationary items
for (i in 0..360 step 360 / staticItemCount) {
rotate(i.toFloat()) {
drawRect(
color = staticItemColor,
topLeft = topLeftOffset,
size = size,
)
}
}
// Dynamic items
for (i in 0..animatedItemCount) {
// angle is cast to into move in intervals of static items
rotate((angle.toInt() + i) * coefficient) {
drawRect(
color = dynamicItemColor.copy(
alpha = (0.2f + 0.15f * i).coerceIn(
0f, 1f
)
),
topLeft = topLeftOffset,
size = size,
)
}
}
}
}
val infiniteTransition = rememberInfiniteTransition()
is used for infinite transition animation and RepeatMode.Restart means the animation restarts giving a feel of infinite animation.
rotate((angle.toInt() + i) * coefficient) {
where you rotate the items. All the angle caluclations are in radians.
You can customize this to your needs. You don't need a image. Draw lines or rect and then animate few of them by rotating it.