🌟

Jetpack ComposeでShimmer Effectを作る

2023/09/06に公開

はじめに

Shimmer Effectとは画像のような、スケルトンスクリーンをキラキラ光らせるエフェクトです。

コンテンツのロード中であることがより視覚的にわかるのでユーザーエクスペリエンスの向上が期待できます。

実装

fun Modifier.shimmer(
    animationSpec: InfiniteRepeatableSpec<Float> = infiniteRepeatable(
        animation = tween(1500)
    ),
    colors: List<Color> = listOf(
        Color(0xFFB8B5B5),
        Color(0xFFC0C0C0),
        Color(0xFFD3D3D3),
        Color(0xFF808080),
        Color(0xFFB8B5B5)
    )
): Modifier = composed {

    var size by remember { mutableStateOf(IntSize(0,0)) }
    val startOffsetX by rememberInfiniteTransition().animateFloat(
        initialValue = -2 * size.width.toFloat(),
        targetValue = 2 * size.width.toFloat(),
        animationSpec = animationSpec
    )

    val linearGradient = Brush.linearGradient(
        colors = colors,
        start = Offset(startOffsetX, 0f),
        end = Offset(startOffsetX + size.width.toFloat(), size.height.toFloat())
    )

    background(linearGradient)
        .onGloballyPositioned { size = it.size }
}

引数

  • animationSpecはエフェクトが始まってから終わるまでの所要時間です。デフォルトで1500msとしていますが、お好みで変更可能です。およそ1000ms~1500ms位が妥当だと思われます。
  • colorsはエフェクトで遷移する色の配列です。最初の要素と最後の要素は同じ色にしておくとスムーズに感じます

内部

  • composed{ }関数はModifierメソッド内でrememberなどのステートフルな状態を扱う関数を使用可能にしてくれます。通常、rememberなどのComposable関数は@Composableアノテーションがついていない関数では使うことはできませんがcomposed{ }はそれを可能にしてくれます
  • sizeはエフェクトで使用する要素のサイズです。遷移中もrememberで常に追跡します。
  • startOffsetXでは、エフェクトの横方向のオフセットを制御しています。Jetpack Composeで無限アニメーションを扱うときはrememberInfiniteTransitionを使うといいでしょう。
    https://developer.android.com/jetpack/compose/animation/value-based#rememberinfinitetransition
    • 開始位置と終了位置はそれぞれ要素の左外側と右外側に適当に配置しています(size.width.toFloat()に -1.5 ~ -2 を掛けるくらいが妥当かもしれません)
  • linearGradientはなんのアニメーションがどう遷移するかを示しています。エフェクトは斜めに光らせたいのでlinearGradient を使用しています。開始地点は左上、終了地点は要素の右下を設定することで左上から右下にエフェクトが動作しています。
    • startOffsetXの変化に従って、グラデーションが要素内を移動し、キラっと光るエフェクトを作り出しています。
  • background(linearGradient).onGloballyPositioned { size = it.size }では、エフェクトの遷移によって更新される要素のサイズを取得してsizeに代入しています

動作例

試しに次のような適当なLazyColumnでShimmer Effectを走らせてみます

LazyColumn{
        items(10){
            Row(
                modifier = Modifier.padding(12.dp),
                verticalAlignment = Alignment.CenterVertically
            ) {
                Box(modifier = Modifier
                    .padding(start = 8.dp)
                    .size(72.dp)
                    .clip(CircleShape)
                    .shimmer())
                Column(modifier = Modifier.padding(start = 16.dp)) {
                    Box(modifier = Modifier.width(180.dp).height(20.dp).shimmer())
                    Box(modifier = Modifier.padding(top = 8.dp).width(80.dp).height(30.dp).shimmer())
                }
            }
        }
  }

すると次のように動作します

おわりに

でも実際はライブラリを使ったほうがいいよねってはなし
よさげなShimmerのライブラリですどうぞ↓
https://github.com/valentinilk/compose-shimmer

Discussion