DEV Community

myougaTheAxo
myougaTheAxo

Posted on

カスタムModifier完全ガイド — Modifier.composed/ModifierNodeElement/描画

この記事で学べること

カスタムModifier(Modifier.composed、ModifierNodeElement、描画Modifier、条件付きModifier)を解説します。


条件付きModifier

// 条件付きModifier拡張
fun Modifier.conditional(condition: Boolean, modifier: Modifier.() -> Modifier): Modifier {
    return if (condition) then(modifier(Modifier)) else this
}

@Composable
fun ConditionalExample() {
    var isSelected by remember { mutableStateOf(false) }

    Box(
        Modifier
            .size(100.dp)
            .conditional(isSelected) {
                border(2.dp, MaterialTheme.colorScheme.primary, RoundedCornerShape(8.dp))
            }
            .background(MaterialTheme.colorScheme.surface, RoundedCornerShape(8.dp))
            .clickable { isSelected = !isSelected }
            .padding(16.dp)
    ) {
        Text(if (isSelected) "選択中" else "未選択")
    }
}
Enter fullscreen mode Exit fullscreen mode

描画カスタムModifier

fun Modifier.shimmer(): Modifier = composed {
    var size by remember { mutableStateOf(IntSize.Zero) }
    val transition = rememberInfiniteTransition(label = "shimmer")
    val startOffset by transition.animateFloat(
        initialValue = -2f,
        targetValue = 2f,
        animationSpec = infiniteRepeatable(
            animation = tween(1000, easing = LinearEasing)
        ),
        label = "shimmer"
    )

    this
        .onSizeChanged { size = it }
        .drawWithContent {
            drawContent()
            drawRect(
                brush = Brush.linearGradient(
                    colors = listOf(
                        Color.Transparent,
                        Color.White.copy(alpha = 0.3f),
                        Color.Transparent
                    ),
                    start = Offset(size.width * startOffset, 0f),
                    end = Offset(size.width * (startOffset + 1f), size.height.toFloat())
                )
            )
        }
}

@Composable
fun ShimmerPlaceholder() {
    Box(
        Modifier
            .fillMaxWidth()
            .height(100.dp)
            .clip(RoundedCornerShape(8.dp))
            .background(Color.LightGray)
            .shimmer()
    )
}
Enter fullscreen mode Exit fullscreen mode

よく使うカスタムModifier

// 丸影付きクリック
fun Modifier.clickableWithRipple(onClick: () -> Unit): Modifier = composed {
    clickable(
        interactionSource = remember { MutableInteractionSource() },
        indication = ripple(bounded = true)
    ) { onClick() }
}

// デバッグ枠線
fun Modifier.debugBorder(color: Color = Color.Red): Modifier =
    border(1.dp, color)

// 最大幅制限
fun Modifier.maxContentWidth(maxWidth: Dp = 600.dp): Modifier =
    fillMaxWidth().widthIn(max = maxWidth)
Enter fullscreen mode Exit fullscreen mode

まとめ

パターン 用途
composed Composable状態を使うModifier
drawWithContent 描画カスタム
onSizeChanged サイズ取得
拡張関数 再利用可能なModifier
  • composedでremember/animationを使うModifier
  • drawWithContentで描画前後にカスタム描画
  • 条件付きModifierで動的スタイル切り替え
  • 拡張関数でプロジェクト共通のModifier定義

8種類のAndroidアプリテンプレート(カスタムUI対応)を公開しています。

テンプレート一覧Gumroad

関連記事:


I publish 8 Android app templates on Gumroad.

Browse templatesGumroad

Top comments (0)