https://kotlinlang.org logo
#compose
Title
# compose
a

Arpan Sarkar

09/13/2020, 5:28 AM
I am trying to create cube grid animation like the first video, In my implementation for first time the animation is working as expected, after the animation repeat everyting is becoming mess...i guess when using
repeatable{}
the keyframe delay is not working as expected.
Copy code
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
                    )
                )
            }
        }
    }
}
s

Sergey Y.

09/13/2020, 9:13 AM
TIP: Use gist to share code. https://gist.github.com/
☝️ 2
d

Doris Liu

09/14/2020, 7:45 PM
Here the
repeatable
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.
For the time being, I'd recommend changing
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. 🙂
4 Views