2
1

More than 1 year has passed since last update.

Jetpack Compose の Modifier.drawBehind でかんたんなドロップシャドウ技

Posted at

Jetpack Compose の Modifier には Modifier.drawBehind というのがあって、
Composable の裏に Canvas を使って描画するものがあります。

Modifier.drawWithContent もあって、
これは内容を明示的に描画することで全景にも描画できるようになっているものです。

これをつかってかんたんなドロップシャドウを描画しましょう。

Notes_230227_152708-2.jpg

まずそのまま描画する

今回は例としてこういう Composable を用意します:

MyComposable.kt
@Composable
fun MyComposable(name: String) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        modifier = Modifier.padding(16.dp),
    ) {
        MyImage()
        Spacer(modifier = Modifier.width(16.dp))
        Text(text = "Hello $name!")
    }
}

@Composable
fun MyImage(
    modifier: Modifier = Modifier,
) {
    Image(
        painter = painterResource(id = R.drawable.my_thumbnail),
        contentDescription = null,
        modifier = modifier,
    )
}

MyComposablePreview

こういうテンプレートにちょっとサムネイル画像を追加したみたいなものがあるとします。

ドロップシャドウを加える

ここでこのサムネイル画像にドロップシャドウを加えたいと思います。

単純に Box の中に二つの同じ composable を配置してズラすとこでも可能かもしれませんがすこしやっかいかもしれません。

ここで Modifier.drawWithCache 内でonDrawBehind により、composable の後ろに Canvas で影を描画することで実現してみたいと思います:

MyComposable.kt
+ import androidx.compose.material3.MaterialTheme
+ import androidx.compose.ui.draw.drawWithCache
+ import androidx.compose.ui.geometry.Offset

 fun MyImage(
     modifier: Modifier = Modifier,
 ) {
+    val color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.10f)
     Image(
         painter = painterResource(id = R.drawable.my_thumbnail),
         contentDescription = null,
-        modifier = modifier,
+        modifier = modifier.drawWithCache {
+            onDrawBehind {
+                drawRect(
+                    color = color,
+                    topLeft = Offset(
+                        x = 8.dp.toPx(),
+                        y = 8.dp.toPx(),
+                    ),
+                    size = size,
+                )
+            }
+        },
     )
 }

この結果はこのようになり影が落ちているように見えます:

MyComposablePreview (ドロップシャドウ版)

ここで描画された canvas は composable の領域をはみでてもクリップされないのでスペースは少しとってあげるのがよいでしょう。

Modifier.clipToBounds すると領域でクリップされます。

drawWithContent

そして Modifier.drawWithContent を使うと、内容の上のレイヤーにも描画できます:

val color = Color.Red
onDrawWithContent {
    drawContent()
    drawRect(
        color = color,
        topLeft = Offset(
            x = 8.dp.toPx(),
            y = 8.dp.toPx(),
        ),
        size = size,
    )
}

drawWithContent

装飾を上に追加したい場合はよさそうですね。

ポイント: 影の色

影の色は onSurface の 10% alpha で塗っていますが、当然上に塗っていくと乗算されていくので、alpha をもった図形を重ねる場合は少し工夫が必要です。

alpha rect on alpha

onDrawBehind {
    drawRect(
        color = color,
        topLeft = Offset(
            x = 8.dp.toPx(),
            y = 8.dp.toPx(),
        ),
        size = size,
    )
    drawRect(
        color = color,
        topLeft = Offset(
            x = 16.dp.toPx(),
            y = 16.dp.toPx(),
        ),
        size = size,
    )
}

とはいえアルファブレンドしないで固定色指定にすると Dark Theme のときの表示が微妙になります:

static color

val color = Color(0xFF555555)

こういうときは Color.compositeOver をキメます:

val color = MaterialTheme.colorScheme.onSurface
    .copy(alpha = 0.10f)
    .compositeOver(MaterialTheme.colorScheme.surface)

そうすると surfaceonSurface を乗算したあとの色で描画できるので、現在のカラースキームに応じた色を使えるのでいい感じですね:

compositeOver on Light compositeOver on Dark

これで色を混ぜることができるので応用が効きそうな感じがしてきましたね。

最後に

以上です。

意外と簡単に composable に装飾を加えることができるので、これ以外でも応用が効きそうですね。活用法をお待ちしております。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1