Jetpack Compose の Modifier には Modifier.drawBehind
というのがあって、
Composable の裏に Canvas を使って描画するものがあります。
Modifier.drawWithContent
もあって、
これは内容を明示的に描画することで全景にも描画できるようになっているものです。
これをつかってかんたんなドロップシャドウを描画しましょう。
まずそのまま描画する
今回は例としてこういう Composable を用意します:
@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,
)
}
こういうテンプレートにちょっとサムネイル画像を追加したみたいなものがあるとします。
ドロップシャドウを加える
ここでこのサムネイル画像にドロップシャドウを加えたいと思います。
単純に Box
の中に二つの同じ composable を配置してズラすとこでも可能かもしれませんがすこしやっかいかもしれません。
ここで Modifier.drawWithCache
内でonDrawBehind
により、composable の後ろに Canvas で影を描画することで実現してみたいと思います:
+ 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,
+ )
+ }
+ },
)
}
この結果はこのようになり影が落ちているように見えます:
ここで描画された 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,
)
}
装飾を上に追加したい場合はよさそうですね。
ポイント: 影の色
影の色は onSurface
の 10% alpha で塗っていますが、当然上に塗っていくと乗算されていくので、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 のときの表示が微妙になります:
val color = Color(0xFF555555)
こういうときは Color.compositeOver
をキメます:
val color = MaterialTheme.colorScheme.onSurface
.copy(alpha = 0.10f)
.compositeOver(MaterialTheme.colorScheme.surface)
そうすると surface
に onSurface
を乗算したあとの色で描画できるので、現在のカラースキームに応じた色を使えるのでいい感じですね:
これで色を混ぜることができるので応用が効きそうな感じがしてきましたね。
最後に
以上です。
意外と簡単に composable に装飾を加えることができるので、これ以外でも応用が効きそうですね。活用法をお待ちしております。