Jetpack ComposeのContentColorを活用する

こんにちは。エキサイト株式会社 Androidエンジニアの克です。

今回は、ContentColorを使って色の変更をシンプルにするお話をします。

まずは普通に要素を表示してみる

とりあえず適当なアイコンとテキストを表示するコードを用意しました。

Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.Center,
) {
    Column {
        Row(
            modifier = Modifier.padding(8.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Icon(
                imageVector = Icons.Rounded.Android,
                contentDescription = null,
            )
            Text(text = "サンプル1")
        }
        Row(
            modifier = Modifier.padding(8.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Icon(
                imageVector = Icons.Rounded.Android,
                contentDescription = null,
            )
            Text(text = "サンプル2")
        }
    }
}

こちらを実行すると次のような画面になります。 f:id:katsuhiro-ito:20210826173914p:plain:w320

もしこの状態で「上側の要素の背景を黒くしたい」という要件が出てきたらどのようにするでしょうか。

単純にRowの背景を黒くしてみます。

---
Row(
    modifier = Modifier
        .background(Color.Black)
        .padding(8.dp),
    verticalAlignment = Alignment.CenterVertically,
) { 
---

f:id:katsuhiro-ito:20210826173923p:plain:w320

アイコンとテキストは黒のままなので、当然ですが見えなくなってしまいます。

そのためアイコンとテキストの色も変えていきます。

---
Icon(
    imageVector = Icons.Rounded.Android,
    contentDescription = null,
    tint = Color.White,
)
Text(
    text = "サンプル1",
    color = Color.White,
)
---

f:id:katsuhiro-ito:20210826173932p:plain:w320

これで要件を満たすことはできましたが、このやり方には下記のような問題点があります。

  • 要素が増えた場合、全ての要素に色の設定をする必要がある
  • 設定の漏れにより、意図しない表示になりやすい

ContentColorを使うようにすると、こういった問題を解決することができます。

ContentColorとは

既存で用意されているComposableの多くは、デフォルトでContentColorを参照するものが多いです。

@Composable
fun Icon(
---
    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
)
@Composable
fun Text(
---
) {
    val textColor = color.takeOrElse {
        style.color.takeOrElse {
            LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
        }
    }

例であげたものでは LocalContentColor.currentが使われています。

こちらは現在設定されているContentColorを参照するというもので、このContentColorを変更してあげれば自動的に要素に反映されるということになります。

ContentColorを使ってみる

先程のコードを、ContentColorを使用したものに変更してみましょう。

鍵となるのはSurfaceです。

SurfaceBoxなどと同様に要素を配置できるものですが、名前の通り要素の下に敷くような用途で使用します。

Surfaceでは自身の色とともに、ContentColorを設定することもできるので今回はこちらを活用していきます。

公式のドキュメントでも、背景色の設定にはSurfaceを使用することが記載されています。

developer.android.com

要素の背景色を設定する際は、Surface を使用することをおすすめします。Surface は適切なコンテンツ色を設定します。Modifier.background() で直接呼び出すと適切なコンテンツ色が設定されないため、ご注意ください。

先程のコードをSurfaceに置き換えたものが下記となります。

Surface(
    color = Color.Black,
    contentColor = Color.White,
) {
    Row(
        modifier = Modifier.padding(8.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Icon(
            imageVector = Icons.Rounded.Android,
            contentDescription = null,
        )
        Text(text = "サンプル1")
    }
}

要素自体に色を指定する必要がなくなったので、どれだけ要素が増えようとも困ることはありませんね。

最後に

個別の対応や同じコードの繰り返しを多用すると、仕様の変更に対応しにくくなったり人為的なミスが発生しやすくなります。 フレームワークが用意してくれている共通化できるような機能を積極的に活用していくようにしましょう。