Jetpack Compose アニメーションが内部でどのように実装されているかを分析する

序文

Compose のアニメーション API は非常に使いやすく、その効果は素晴らしく見えますが、内部ではどのように機能しているのでしょうか?

アニメーションを使用したコード例:

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 = "点我进行位移")
}

  

offsetAnimation アニメーションを制御する Boolean 型の isOffset 状態があり、次にアニメーションが Button のオフセットを制御し、最終的にアニメーション効果を実現することがわかります。

文章

主に animateDpAsState(animate*AsState) が何をするかを調べます

animateDpAsState に従って、最後に animateValueAsState メソッドに入ります。

 メソッド内に Animatable アニメーション クラスが作成されます。

次に、上の図の矢印に従って、targetValue が変更された後、targetValue 値の変更がチャネルを介して送信され、launch がコルーチンを開始し、その中で animatable の animateTo メソッドを呼び出すことを確認します

次に、Animatable クラスがどのようにアニメーション化されるかを見てみましょう。

主なことは、AnimateState 型 (State) の internalState 変数を保持することです。次に、animateTo メソッドを使用して上記のコードに従います。

 

 言うことは何もありません。targetValue をラップし、runAnimation メソッドを呼び出して、フォローアップします (2 つの図に分割することはできません)。

 主なロジックは endState.animate() です。
endState は、コピーされた Animatable 内の AnimationState オブジェクト internalState であり、アニメーションの初期値です。

アニメーションは、animateTo メソッドでアニメーション ターゲット値をラップするオブジェクトです。

次に、animate メソッドに従います。

このメソッドは主に 2 つの部分に分かれており、最初の部分は、アニメーションに必要な情報を格納するためのデータ構造である AnimationScope を構築することです。

 2 番目の部分は、実際のアニメーションが計算されて有効になる場所、doAnimationFrame です。

まず、AnimationScope が初めて作成されたときに 1 回実行されます (または callWithFrameNanos メソッドを介して次のフレームで 1 回実行されます)。

その後、アニメーションが実行されていない場合はループ (while) し続けると判断し、フレームごとに doAnimationFrame を実行してアニメーションの値を計算します。

 doAnimationFrame メソッド コード:

 このうち、現在のアニメーションに設定する必要がある値は、lastFrameTimeNanos や value などの属性を含む時間などのパラメーターを介して計算され、最後に updateState メソッドが呼び出され、AnimationScope の値が AnimationState に更新されます。

updateState メソッド コード:

 この状態オブジェクトは、実際には animateDpAsState の Animatable アニメーション クラスの AnimationState オブジェクト internalState です。

したがって、上記は実際には Compose でのアニメーションの簡略化されたプロセスです

要約する

 since AnimationState is a State, it will automatically monitor its changes when used in Compose. その値が変更される限り、対応する位置が再編成され、Composable は新しい値を使用して、次のようなさまざまな効果を表示します。最初のサンプル コード:

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 = "点我进行位移")
}

 isOffset を変更した後、アニメーションの計算と内部状態の値の変更により、animateDpAsState の値が再度呼び出され、ボタンのオフセット関数が常に呼び出され、ボタンが下に移動し続けます。

実際、Compose のアニメーションがそれほど多くのことを考慮していない場合は、次のコードに簡略化できます。

    /**
     * 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
            }
        }
    }

使用方法は次のとおりです。効果は例と同様です。

 追伸: この API をオンライン プロジェクトに使用することはお勧めしません. システムを使用することをお勧めします. 使用する場合は、それを参照することもできます (星歓迎): ComposeViews/MAnimator.kt at main ltttttttttttt/  ComposeViews (github.com)

分析に誤りがありましたらご指摘ください

終わり

おすすめ

転載: blog.csdn.net/qq_33505109/article/details/126623549