Analizar cómo se implementa internamente la animación de Jetpack Compose

prefacio

La API de animación de Compose es muy fácil de usar y el efecto se ve increíble, entonces, ¿cómo funciona internamente?

Ejemplo de código usando animación:

var isOffset by remember { mutableStateOf(false) }
val offsetAnimation by animateDpAsState(targetValue = if (isOffset) 100.dp else 0.dp)
Button(
    onClick = { isOffset = !isOffset },
    modifier = Modifier.offset(0.dp, offsetAnimation)
) {
    Text(text = "点我进行位移")
}

  

Se puede ver que hay un estado isOffset de tipo booleano, que controla la animación offsetAnimation, y luego la animación controla el desplazamiento del Botón, y finalmente realiza el efecto de animación.

texto

Nos fijamos principalmente en lo que hace animateDpAsState(animate*AsState)

Siga animateDpAsState y finalmente ingrese el método animateValueAsState:

 Se crea una clase de animación Animatable dentro del método.

Luego seguimos la flecha en la figura anterior para ver que después de que el valor de destino cambia, el cambio del valor de valor de destino se enviará a través del canal, y luego el lanzamiento inicia una corrutina y llama al método animateTo de animable en ella

Entonces echemos un vistazo a cómo se anima la clase Animatable:

Lo principal es mantener una variable internalState de tipo AnimationState (Estado), y luego seguimos el código anterior con el método animateTo:

 

 No hay nada que decir, envuelva el targetValue, luego llame al método runAnimation y luego haga un seguimiento (no se puede dividir en dos imágenes):

 La lógica principal es endState.animate()
endState es el objeto AnimationState internalState en el Animatable copiado, que es el valor inicial de la animación.

Luego, la animación es el objeto que envuelve el valor objetivo de la animación en el método animateTo

Luego sigue el método animado:

Este método se divide principalmente en dos partes: la primera parte es construir un AnimationScope, una estructura de datos, para almacenar la información necesaria para la animación.

 La segunda parte es donde se calcula y tiene efecto la animación real, doAnimationFrame

Primero, se ejecutará una vez cuando se cree AnimationScope por primera vez (o se ejecutará una vez en el siguiente cuadro, a través del método callWithFrameNanos)

Luego juzgará que si la animación no se ha ejecutado, seguirá en bucle (mientras), ejecutando doAnimationFrame cuadro por cuadro para calcular el valor de la animación.

 código del método doAnimationFrame:

 Entre ellos, el valor que debe establecerse para la animación actual se calcula a través de parámetros como el tiempo, incluidos atributos como lastFrameTimeNanos y value, y luego se llama finalmente al método updateState para actualizar el valor de AnimationScope a AnimationState.

código del método updateState:

 Este objeto de estado es en realidad el objeto AnimationState internalState en la clase de animación Animatable en animateDpAsState

Entonces, lo anterior es en realidad el proceso simplificado de animación en Compose

Resumir

 Dado que AnimationState es un estado, monitoreará automáticamente sus cambios cuando se use en Compose. Siempre que su valor cambie, hará que la posición correspondiente se reorganice, y luego Composable usará el nuevo valor para mostrar diferentes efectos, como el código de muestra inicial:

var isOffset by remember { mutableStateOf(false) }
val offsetAnimation by animateDpAsState(targetValue = if (isOffset) 100.dp else 0.dp)
Button(
    onClick = { isOffset = !isOffset },
    modifier = Modifier.offset(0.dp, offsetAnimation)
) {
    Text(text = "点我进行位移")
}

 Después de modificar isOffset, el valor en animateDpAsState se volverá a llamar debido al cálculo de la animación y la modificación del valor del estado interno, lo que hace que la función de desplazamiento del botón se llame todo el tiempo, haciendo que el botón siga moviéndose hacia abajo.

De hecho, si la animación en Compose no considera tantas cosas, se puede simplificar al siguiente código:

    /**
     * creator: lt
     * effect : 自定义的动画播放器,逻辑更简单
     * warning:
     * [initialValueWithState]动画要改变的状态,起始动画值为其value值
     * [targetValue]要通过动画转化到的目标值
     * [duration]动画的持续时间
     */
    @OptIn(ExperimentalComposeApi::class)
    suspend fun animateWithFloat(
        initialValueWithState: MutableState<Float>,
        targetValue: Float,
        duration: Int = AnimationConstants.DefaultDurationMillis,
    ) {
        //动画起始值,目标差值
        val startValue = initialValueWithState.value
        val valueToBeTransformed = targetValue - startValue
        //动画起始时间,持续时间
        val startTime = System.nanoTime()
        val duration = duration * 1000000L
        //通过循环在下一帧计算动画的值
        val frameClock = coroutineContext.monotonicFrameClock
        while (System.nanoTime() <= startTime + duration) {
            frameClock.withFrameNanos {
                //计算动画的值,并设置值给状态
                val progress = minOf(it - startTime, duration).toFloat() / duration
                val increase = progress * valueToBeTransformed
                initialValueWithState.value = startValue + increase
            }
        }
    }

El uso es el siguiente, el efecto es similar al ejemplo:

 PD: No se recomienda usar esta API para proyectos en línea. Es mejor usar el sistema. Si desea usarlo, también puede consultarlo (estrella de bienvenida): ComposeViews/MAnimator.kt en main ltttttttttttt/  ComposeViews (github.com)

Si hay algo mal con el análisis, por favor indíquelo.

fin

Supongo que te gusta

Origin blog.csdn.net/qq_33505109/article/details/126623549
Recomendado
Clasificación