【Jetpack Compose】Composeをオーバーレイ表示する


要素の上に半透明で表示するオーバーレイ表示をしてみました。
サイズが固定の場合はとても簡単なのですが、サイズが不定の場合はちょっとコツ(?)が必要でした。

また、オーバーレイと言っても色々あると思うのですが、この記事で扱うのは要素の上に同じサイズで被せるオーバーレイです。
下の画像のカードラベル表示のComposeに対して、半透明の白色のComposeをオーバーレイ表示しています。

固定サイズの場合

固定サイズの場合は、そのComposeの上に fillMaxSize() で半透明のComposeを重ねるだけです。

例えば、こんなカード表示のComposeの場合です。
横幅は引数のModifierで指定できますが、縦幅は80dpで固定指定になっています。

@Composable
fun Card(modifier: Modifier = Modifier) {
    Card(modifier = modifier.height(80.dp), shape = RoundedCornerShape(8.dp), elevation = 4.dp) {
        Row(modifier = Modifier.fillMaxWidth()) {
            Image(
                painter = rememberImagePainter("https://placehold.jp/3d4070/ffffff/80x80.png?text=Image"),
                contentDescription = null,
                modifier = Modifier.size(80.dp)
            )

            Column(modifier = Modifier
                .fillMaxSize()
                .padding(8.dp)) {
                Text("Title", fontSize = 22.sp)
                Text("description", fontSize = 16.sp)
            }
        }
    }
}

こういうサイズが中身の要素に関わらず固定の場合は、 fillMaxSize() で上乗せしてあげればすんなり実装できます。

@Composable
fun Card(modifier: Modifier = Modifier, isOverlay: Boolean = false) {
    Card(modifier = modifier.height(80.dp), shape = RoundedCornerShape(8.dp), elevation = 4.dp) {
        Row(modifier = Modifier.fillMaxWidth()) {
            Image(
                painter = rememberImagePainter("https://placehold.jp/3d4070/ffffff/80x80.png?text=Image"),
                contentDescription = null,
                modifier = Modifier.size(80.dp)
            )

            Column(modifier = Modifier
                .fillMaxSize()
                .padding(8.dp)) {
                Text("Title", fontSize = 22.sp)
                Text("description", fontSize = 16.sp)
            }
        }

        // オーバーレイ
        if (isOverlay) {
            Box(modifier = Modifier
                .fillMaxSize()
                .background(Color(0x88FFFFFF)))
        }
    }
}

半透明は白に対してアルファ値を下げてあげれば透過します。白透過でなくて黒透過にしたければ、適当に 0x80000000 とか指定すればできます。

サイズが不定の場合

今度はサイズが不定の場合です。こういう感じで、テキストによってサイズが高さが可変するケースです。
(Imageあたりのデザインがあれなのは目をつぶります)

@Composable
fun Card(modifier: Modifier = Modifier, description: String) {
    Card(
        modifier = modifier.wrapContentHeight(),
        shape = RoundedCornerShape(8.dp),
        elevation = 4.dp
    ) {
        Row(modifier = Modifier.fillMaxWidth()) {
            Image(
                painter = rememberImagePainter("https://placehold.jp/3d4070/ffffff/80x80.png?text=Image"),
                contentDescription = null,
                modifier = Modifier.size(80.dp)
            )

            Column(
                modifier = Modifier
                    .wrapContentHeight()
                    .padding(8.dp)
            ) {
                Text("Title", fontSize = 22.sp)
                Text(description, fontSize = 16.sp, softWrap = true)
            }
        }
    }
}

この場合、先程と同様に fillMaxSize() で上乗せしてみると上手くいかないことがわかります。

こんな感じで、子のBoxのサイズが親のCardのサイズを広げてしまうような挙動になってしまっています。
Overlay表示するBoxのサイズは、親のサイズと同じサイズになってほしいところです。汎用的なModifierには残念ながらそういうものはないのですが、BoxScope限定で使えるModifierに matchParentSize() を使えば実現できます。

Box {
    // Boxの子要素限定で使えるModifier
    // Box サイズに影響を与えずに子を親のBoxと同じサイズにする
    Box(modifier = Modifier.matchParentSize()) {}
}

しれっと下記のドキュメントにも記載されてます。
https://developer.android.com/jetpack/compose/layouts/basics?hl=ja#type-safety

このModifierを使うにはBoxScopeである必要があるので、Cardの直下にBoxを置いてあげます。そしてOverlay表示しているBoxのmodifierに matchParentSize() を追加してあげます。

@Composable
fun Card(modifier: Modifier = Modifier, description: String, isOverlay: Boolean = false) {
    Card(
        modifier = modifier.wrapContentHeight(),
        shape = RoundedCornerShape(8.dp),
        elevation = 4.dp
    ) {
        Box(Modifier.fillMaxWidth()) {
            Row(modifier = Modifier.fillMaxWidth()) {
                Image(
                    painter = rememberImagePainter("https://placehold.jp/3d4070/ffffff/80x80.png?text=Image"),
                    contentDescription = null,
                    modifier = Modifier.size(80.dp)
                )

                Column(
                    modifier = Modifier
                        .wrapContentHeight()
                        .padding(8.dp)
                ) {
                    Text("Title", fontSize = 22.sp)
                    Text(description, fontSize = 16.sp, softWrap = true)
                }
            }

            if (isOverlay) {
                Box(
                    modifier = Modifier
                        // 親のサイズいっぱいに広げる
                        .matchParentSize()
                        .background(Color(0x88FFFFFF))
                )
            }
        }
    }
}

いい感じにできました💪