Arpan Sarkar
09/13/2020, 5:28 AMrepeatable{}
the keyframe delay is not working as expected.import android.annotation.SuppressLint
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.core.*
import androidx.compose.animation.transition
import androidx.compose.foundation.Box
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.unit.dp
class CubeAnimationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Surface(color = Color(0xFFD55400), modifier = Modifier.fillMaxSize()) {
Box(gravity = Alignment.Center) {
CubeProgress(modifier = Modifier.preferredSize(100.dp))
}
}
}
}
}
private const val AnimationDuration = 1300
private const val AnimationDelayMultiplier = 100
private const val CubePerRowCol = 3
private enum class ProgressState { End, Start }
private val scales = Array(CubePerRowCol * CubePerRowCol) {
FloatPropKey()
}
private fun calculateDelay(i: Int): Int {
val col = i % CubePerRowCol
val row = i / CubePerRowCol
return AnimationDelayMultiplier * ((CubePerRowCol - row - 1) + col)
}
@SuppressLint("Range")
private val definition = transitionDefinition<ProgressState> {
state(ProgressState.Start) {
for (element in scales) {
this[element] = 1f
}
}
state(ProgressState.End) {
for (element in scales) {
this[element] = 1f
}
}
transition(fromState = ProgressState.Start, toState = ProgressState.End) {
for (i in scales.indices) {
scales[i] using repeatable(
iterations = AnimationConstants.Infinite,
animation = keyframes {
1f at (AnimationDuration * 0f).toInt() with FastOutSlowInEasing
0f at (AnimationDuration * 0.35f).toInt() with FastOutSlowInEasing
1f at (AnimationDuration * 0.7f).toInt() with FastOutSlowInEasing
1f at (AnimationDuration * 1f).toInt() with FastOutSlowInEasing
durationMillis = AnimationDuration
delayMillis = calculateDelay(i)
}
)
}
}
}
@SuppressLint("Range")
@Composable
fun CubeProgress(modifier: Modifier) {
val transitionState =
transition(
definition = definition,
toState = ProgressState.End,
initState = ProgressState.Start
)
Canvas(modifier = modifier) {
val cubeWidth = size.width * 1f / CubePerRowCol
val cubeHeight = size.height * 1f / CubePerRowCol
val halfCubeWidth = cubeWidth / 2
val halfCubeHeight = cubeWidth / 2
for (i in 0 until CubePerRowCol * CubePerRowCol) {
val topOffset = Offset(
x = (i % CubePerRowCol) * cubeWidth,
y = (i / CubePerRowCol) * cubeHeight
)
val scaleX = transitionState[scales[i]]
val scaleY = transitionState[scales[i]]
val pivotX = topOffset.x + halfCubeWidth
val pivotY = topOffset.y + halfCubeHeight
scale(
scaleX = scaleX,
scaleY = scaleY,
pivotX = pivotX,
pivotY = pivotY
) {
drawRect(
color = Color.White,
topLeft = topOffset,
size = Size(
width = cubeWidth,
height = cubeHeight
)
)
}
}
}
}
Sergey Y.
09/13/2020, 9:13 AMDoris Liu
09/14/2020, 7:45 PMrepeatable
repeats everything defined in the supplied AnimationSpec
(in this case keyframes
), including start delay. However, to achieve the desired effect in your screenshots, you'll need all animations to finish at the same time. They are not currently ending at the same time since they have the same duration but different start delay. Ideally, we should have a "repeatable transition" for this use case (instead of repeating each property animation independently). That way we could repeat when all the sub-animations have finished.durationMillis
for the keyframes to be AnimationDuration + longestDelay - calculateDelay(i)
. The rest of the keyframes configuration can stay exactly as is. Let me know if that works for you. 🙂