アニメーションの動きに特殊な効果を加えることができます。
特殊な効果とは、動きが加速したり、減速したり、弾んだり、または向きを変えたり、などです。
上記のような特殊な効果は、動きの軌道がそれぞれ異なる形になるため、ここでは「アニメーションの形状」と表現しています。
AnimationSpecはアニメーションの形状を定義するインターフェースです。そして、Jetpack Composeは実装済みの形状を4つ提供しています。
この中から、「tween」と取りあげて、まとめます。
※環境:Android Studio Iguana | 2023.2.1 Patch 1
Kotlin 1.9.0
Compose Compiler 1.5.1
androidx.compose.animation:* 1.5.0
アニメーションの形状
AnimationSpecはアニメーションの形状を定義するインターフェースです。
interface AnimationSpec<T> {
fun <V : AnimationVector> vectorize(
converter: TwoWayConverter<T, V>
): VectorizedAnimationSpec<V>
}
Jetpack Composeは実装済みの形状を4つ提供しています。
形状 | 概要 | 補足 |
tween | 滑らかな曲線を描いて変化 | 3次のベジェ曲線 |
spring | バネが振動するように変化 | バネの減衰比(振動の振幅)を選択可能
バネの硬さ(振動の速さ)を選択可能 |
keyframes | 折れ線を描いて変化 | |
snap | 指定時間に即座に切り替え | |
この中のtweenは、滑らかな曲線を描いて変化するアニメーションを提供します。tweenの実態はAnimationSpecを実装したTweenSpecクラスです。
@Stable
fun <T> tween(
durationMillis: Int = DefaultDurationMillis,
delayMillis: Int = 0,
easing: Easing = FastOutSlowInEasing
): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing)
private val StartX = 20.dp
private val EndX = 260.dp
@Preview
@Composable
private fun AnimationSpecSample() {
Column {
val _toggle = remember { mutableStateOf(false) }
Spacer(modifier = Modifier.height(20.dp))
// ----------------------------------------------------------
...
Text(text = "[ tween ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(durationMillis = 1000)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
...
// ----------------------------------------------------------
Spacer(modifier = Modifier.height(20.dp))
Button(onClick = { _toggle.value = !_toggle.value }) {
Text(text = "Toggle")
}
}
}
関数の引数
tween関数は次のような引数を持ち、形状を調整できます。
引数 | 概要 |
durationMillis | Int | アニメーションの継続時間 |
delayMillis | Int | アニメーションの開始までの待機時間 |
easing | Easing | 曲線のアルゴリズム
・定義済みパラメータ
FastOutSlowInEasing
LinearOutSlowInEasing
FastOutLinearInEasing
LinearEasing |
継続・待機時間(durationMillis, delayMillis)
durationMillisはアニメーションの継続時間を、delayMillisはアニメーション開始までの待機時間を指定します。
例:durationMillisの違い
durationMillisのサンプルコード
private val StartX = 20.dp
private val EndX = 260.dp
@Preview_mdpi320x480
@Composable
private fun DurationSample() {
Column {
val _toggle = remember { mutableStateOf(false) }
Spacer(modifier = Modifier.height(20.dp))
// ----------------------------------------------------------
Text(text = "[ Duration 500ms ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(durationMillis = 500)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
// ----------------------------------------------------------
Text(text = "[ Duration 1000ms ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(durationMillis = 1000)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
// ----------------------------------------------------------
Text(text = "[ Duration 1500ms ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(durationMillis = 1500)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
// ----------------------------------------------------------
Spacer(modifier = Modifier.height(20.dp))
Button(onClick = { _toggle.value = !_toggle.value }) {
Text(text = "Toggle")
}
}
}
例:delayMillisの違い
delayMillisのサンプルコード
private val StartX = 20.dp
private val EndX = 260.dp
@Preview_mdpi320x480
@Composable
private fun DelaySample() {
Column {
val _toggle = remember { mutableStateOf(false) }
Spacer(modifier = Modifier.height(20.dp))
// ----------------------------------------------------------
Text(text = "[ Delay 200ms ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(durationMillis = 500, delayMillis = 200)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
// ----------------------------------------------------------
Text(text = "[ Delay 500ms ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(durationMillis = 500, delayMillis = 500)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
// ----------------------------------------------------------
Text(text = "[ Delay 1000ms ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(durationMillis = 500, delayMillis = 1000)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
// ----------------------------------------------------------
Spacer(modifier = Modifier.height(20.dp))
Button(onClick = { _toggle.value = !_toggle.value }) {
Text(text = "Toggle")
}
}
}
曲線のアルゴリズム(easing)
easingは曲線のアルゴリズムを指定します。
Jetpack Composeは表に示す4つの曲線を準備していて、その内の3つは制御点の異なる3次のベジェ曲線です。
パラメータ | アルゴリズム | 定義 |
FastOutSlowInEasing | ベジェ曲線 | CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f) |
LinearOutSlowInEasing | CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f) |
FastOutLinearInEasing | CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f) |
LinearEasing | 直線 | Easing { fraction -> fraction } |
※CubicBezierEasing:3次のベジェ曲線を表現したクラス |
val FastOutSlowInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)
val LinearOutSlowInEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)
val FastOutLinearInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f)
easingのサンプルコード
private val StartX = 20.dp
private val EndX = 260.dp
@Preview_mdpi320x480
@Composable
private fun EasingSample() {
Column {
val _toggle = remember { mutableStateOf(false) }
Spacer(modifier = Modifier.height(20.dp))
// ----------------------------------------------------------
Text(text = "[ FastOutSlowIn ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(
durationMillis = 1000,
easing = FastOutSlowInEasing
)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
// ----------------------------------------------------------
Text(text = "[ LinearOutSlowIn ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(
durationMillis = 1000,
easing = LinearOutSlowInEasing
)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
// ----------------------------------------------------------
Text(text = "[ FastOutLinearIn ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(
durationMillis = 1000,
easing = FastOutLinearInEasing
)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
// ----------------------------------------------------------
Text(text = "[ Linear ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
// ----------------------------------------------------------
Spacer(modifier = Modifier.height(20.dp))
Button(onClick = { _toggle.value = !_toggle.value }) {
Text(text = "Toggle")
}
}
}
ベジェ曲線
ベジェ曲線は始点と終点を結ぶ、滑らかな曲線を描くアルゴリズムです。中点(始点と終点の間にある点)の数と位置より、曲線の軌道が変わります。
アルゴリズム
始点、終点と中点を合わせて制御点と言います。制御点がN個の場合、曲線の式はN-1次式になります。ですので、制御点4個の曲線を、「3次のベジェ曲線」と呼びます。
アルゴリズムはとても簡単です。下記は3次のベジェ曲線を図示したものです。
- (0)制御点を4つ定義(P0:始点、P1,2:中点、P3:終点)
- (1-1)線分P0-P1をt:1-tの比率で分割する点P4を打つ
- (1-2)線分P1-P2をt:1-tの比率で分割する点P5を打つ
- (1-3)線分P2-P3をt:1-tの比率で分割する点P6を打つ
- (2-1)線分P4-P5をt:1-tの比率で分割する点P7を打つ
- (2-2)線分P5-P6をt:1-tの比率で分割する点P8を打つ
- (3-1)線分P7-P8をt:1-tの比率で分割する点P9を打つ
- (4)t←0~1まで(1)~(3)を繰り返した時のP9が作る軌道
CubicBezierEasingクラス
CubicBezierEasingは3次のベジェ曲線を表すクラスです。アニメーションの形状を定義するために作られています。
@Immutable
class CubicBezierEasing(
private val a: Float,
private val b: Float,
private val c: Float,
private val d: Float
) : Easing { ... }
制御点は次のようになります。アニメーションの形状が「一辺が1.0fの正方形」の範囲へ正規化されている点に注意してください。
- P0:(0.0f, 0.0f) … 固定、アニメーションの開始
- P1:( a, b)
- P2:( c, d)
- P3:(1.0f, 1.0f) … 固定、アニメーションの終了
CubicBezierEasingの例
private val StartX = 20.dp
private val EndX = 260.dp
@Preview_mdpi320x480
@Composable
private fun CustomEasingSample() {
Column {
val _toggle = remember { mutableStateOf(false) }
Spacer(modifier = Modifier.height(20.dp))
// ----------------------------------------------------------
Text(text = "[ Custom ]")
Box(
modifier = Modifier
.size(width = 320.dp, height = 50.dp)
.background(color = Color(0xFFF0F0FF)),
contentAlignment = Alignment.CenterStart
) {
val _posX = animateDpAsState(
targetValue = if(_toggle.value) EndX else StartX,
animationSpec = tween(
durationMillis = 1000,
easing = CubicBezierEasing(0.2f, 0.6f, 0.8f, 0.4f)
)
)
Image(
painter = painterResource(R.drawable.baseline_toys_black_36),
contentDescription = null,
modifier = Modifier
.size(36.dp)
.offset(x = _posX.value)
)
}
// ----------------------------------------------------------
Spacer(modifier = Modifier.height(20.dp))
Button(onClick = { _toggle.value = !_toggle.value }) {
Text(text = "Toggle")
}
}
}
関連記事:
animate*AsState関数は制御する値のタイプにより、数種類が準備されています。 どの関数も、animateValueAsStateが原型のラッパー関数であり、動作は同じです。 ここでは、各々の関数について、サンプルを示します。 また、animateValueAsStateのサンプルは、独自の関数を作る方法を紹介しています。 ※環境:Android Studio Iguana | 2023.2.1 Kotlin 1.9.0 Compose Compiler 1.5.1 androidx.compose.animation:* 1.5.0 ...
Jetpack Composeが提供するアニメーションAPIは非常に充実しています。 「どのAPIを使えば最適なのか?」と、その選択を迷うくらいに数が多いです。ドキュメントは、「〇〇占い」に登場するようなYes/Noの設問ツリーを掲載(アニメーションAPIを選択する)して、選択の手助け行っています。 ここでは、アニメーションAPIの中から「animate*AsState(*は型名が入る)」を取りあげて、まとめます。 animate*AsStateは、APIの中で最も汎用性があります。まず始めに抑えておくべきアニメーションAPIです。 ※環境:Android Studio Iguana | 2023.2.1 Kotlin 1.9.0 Compose Compiler 1.5.1 androidx.compose.animation:* 1.5.0 ...
アニメーションの動きに特殊な効果を加えることができます。 特殊な効果とは、動きが加速したり、減速したり、弾んだり、または向きを変えたり、などです。 上記のような特殊な効果は、動きの軌道がそれぞれ異なる形になるため、ここでは「アニメーションの形状」と表現しています。 AnimationSpecはアニメーションの形状を定義するインターフェースです。そして、Jetpack Composeは実装済みの形状を4つ提供しています。 この中から、「spring」と取りあげて、まとめます。 ※環境:Android Studio Iguana | 2023.2.1 Patch 1 Kotlin 1.9.0 Compose Compiler 1.5.1 androidx.compose.animation:* 1.5.0 ...
アニメーションの動きに特殊な効果を加えることができます。 特殊な効果とは、動きが加速したり、減速したり、弾んだり、または向きを変えたり、などです。 上記のような特殊な効果は、動きの軌道がそれぞれ異なる形になるため、ここでは「アニメーションの形状」と表現しています。 AnimationSpecはアニメーションの形状を定義するインターフェースです。そして、Jetpack Composeは実装済みの形状を4つ提供しています。 この中から、「keyframes」と取りあげて、まとめます。 ※環境:Android Studio Iguana | 2023.2.1 Patch 1 Kotlin 1.9.0 Compose Compiler 1.5.1 androidx.compose.animation:* 1.5.0 ...
アニメーションの動きに特殊な効果を加えることができます。 特殊な効果とは、動きが加速したり、減速したり、弾んだり、または向きを変えたり、などです。 上記のような特殊な効果は、動きの軌道がそれぞれ異なる形になるため、ここでは「アニメーションの形状」と表現しています。 AnimationSpecはアニメーションの形状を定義するインターフェースです。そして、Jetpack Composeは実装済みの形状を4つ提供しています。 この中から、「snap」と取りあげて、まとめます。 ※環境:Android Studio Iguana | 2023.2.1 Patch 1 Kotlin 1.9.0 Compose Compiler 1.5.1 androidx.compose.animation:* 1.5.0 ...