序文
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)
分析に誤りがありましたらご指摘ください
終わり