Jetpack ComposeアプリをMaterial Design 3へ移行する

この記事をシェア

この記事では、Jetpack Composeを使ったアプリをMaterial Design 2からMaterial Design 3へ移行する方法を確認します。「Jetpack Compose入門(18) テーマカラーの適用」で作成したTestAppをMaterial Design 3に対応させていきます。

以降、この記事内ではMaterial Design 2と3をそれぞれM2, M3と呼ぶことにします。

M2
M3

M3に移行するメリット・デメリット

最近はGoogle製アプリをはじめとして徐々にM3を採用したアプリが増えてきています。アプリをM3に移行することによって、それらのアプリと比較して遜色のない見た目を実現できるというメリットがあります。また今回は説明しませんが、壁紙の配色に合わせてアプリの配色が動的に変化するDynamic Colorを使えるようになることも大きなメリットになります。

さらに、M3ではJetpack Compose用のレイアウトに、TopAppBarなどのバリエーションが追加されていますので、それらを使って自由度の高いレイアウトを実現できるようになります。

一方でM3はまだ登場してから間もないため、必要なコンポーネントが出そろっていない感じがあります。例えば、Jetpack ComposeのM3にはTextFieldがありません。いずれは出そろうのだと思いますが(そうでないと困る)、それまではM2を誤魔化しながら使うことになると思います。

参考になるページ

M2からM3への移行作業をするにあたって、参考になるページを挙げておきます。移行に必要な情報は、概ねこれらのページに記載されています。

手順の概要

M2からM3への移行にあたってやることは主に次の4つです。

  • Gradleの依存関係の変更
  • Jetpack Composeのテーマの変更
  • Jetpack Composeのコンポーネントの変更
  • 従来のXMLのテーマの変更

順を追って説明していきます。

Gradleの変更

まずは、Gradleのmaterialライブラリのバージョンを変更します。

dependencies {
    implementation "androidx.compose.material3:material3:1.0.0-alpha06"
    implementation 'com.google.android.material:material:1.5.0'
}

Jetpack ComposeのM3コンポーネントは、M2とは別のライブラリとして提供されています。material3ライブラリの依存関係を追加します。material3ライブラリの最新バージョンはこちらで確認できます。記事執筆時点では、1.0.0-alpha06となっています。

また、Jetpack Compose以外の部分をM3に対応させるには、materialライブラリの1.5.0以上が必要です。UIをJetpack Composeで作っていても、ステータスバーの色を指定するためにthemes.xmlに従来のマテリアルライブラリへの依存が残っていたりしますので、こちらも依存関係を追加します。すでに追加されている場合は、バージョンが1.5.0以上になっていることを確認します。

Note: In order to use the new Material3 themes and component styles, you should depend on version 1.5.0 or later.

https://m3.material.io/libraries/mdc-android/getting-started

Composeのテーマを変更

Jetpack ComposeのUIに適用するマテリアルテーマは、Theme.ktに定義されています。これをM2からM3に変更します。

まずはimportするパッケージをandroidx.compose.material.MaterialThemeからandroidx.compose.material3.MaterialThemeに変更します。”3″をつけるだけです。

テーマを設定するMaterialTheme()関数はM2M3で引数が異なるので、M3の形式に合わせて変更します。

@Composable
fun MaterialTheme(
    colors: Colors? = MaterialTheme.colors,
    typography: Typography? = MaterialTheme.typography,
    shapes: Shapes? = MaterialTheme.shapes,
    content: (@Composable () -> Unit)?
): Unit
@Composable
fun MaterialTheme(
    colorScheme: ColorScheme? = MaterialTheme.colorScheme,
    typography: Typography? = MaterialTheme.typography,
    content: (@Composable () -> Unit)?
): Unit

M3では第一引数がColorsからColorSchemeに変更されています。M2のcolorsには、ダークモードのオン・オフを取得して、それぞれdarkColors()lightColors()で作成したカラーパレットを設定するのが一般的でした。M3でも同じように、darkColorScheme()lightColorScheme()という関数が用意されているので、これらを使ってダークモード・ライトモード用のColorSchemeを作成して、システム設定に合わせてどちらかを設定します。

typography引数は見た目は同じですが、M3ではmaterial3パッケージのTypographyオブジェクトを渡す必要があります。

shapes引数は現時点では実装されていませんので削除します。(近日中に追加されるとのことです。)

Theme.ktを移行前後で比較すると以下のようになります。materialパッケージの代わりにmaterial3パッケージをインポートし、Colorsの代わりにColorSchemeを使います。後で説明しますが、xxxVariantという名前の色はM3では廃止されましたので、ColorSchemeには含まれません。M3のMaterialTheme()には今のところshapes引数はありません。

import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors

private val DarkColorPalette = darkColors(
    primary = Purple200,
    primaryVariant = Purple700,
    secondary = Teal200
)

private val LightColorPalette = lightColors(
    ...
)

@Composable
fun TestAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
        colors = colors,
        typography = Typography,
        shapes = Shapes,
        content = content
    )
}
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme

private val DarkColorPalette = darkColorScheme(
    primary = Purple200,
//    primaryVariant = Purple700,
    secondary = Teal200
)

private val LightColorPalette = lightColorScheme(
    ...
)

@Composable
fun TestAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
        colorScheme = colors,
        typography = Typography,
//        shapes = Shapes,
        content = content
    )
}

MaterialTheme()typographyに渡すオブジェクトはTypo.ktで定義しています。こちらも移行前後で比較します。タイポグラフィーは全体的に名前が変更されています(後で説明します)。body1に相当するのはbodyLargeですので、引数名を変更しています。

import androidx.compose.material.Typography

val Typography = Typography(
    body1 = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp
    )
)
import androidx.compose.material3.Typography

val Typography = Typography(
    bodyLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp
    )
)

色の定義の変更

M3ではテーマで定義できる色の種類が大幅に増えました。M2とM3でそれぞれ使える色の定義をMigrating to Material Design 3から転載します。colorPrimaryVariantとcolorSecondaryVariantは廃止されています。先ほどTheme.ktの移行で触れたように、これらの色を使っている場合は対応が必要になります。colorPrimaryVariantはステータスバーの色などに使われることが多かったですが、M3ではステータスバーとTopBarは同じ色にすることが推奨されているので、colorPrimaryVariantが廃止になっています。

M2M3
android:backgroundColorandroid:backgroundColor
colorPrimarycolorPrimary
colorOnPrimarycolorOnPrimary
 colorPrimaryContainer
 colorOnPrimaryContainer
 colorPrimaryInverse
colorPrimaryVariantDEPRECATED
colorPrimarySurfacecolorSurface
colorOnPrimarySurfacecolorOnSurface
colorSecondarycolorSecondary
colorOnSecondarycolorOnSecondary
 colorSecondaryContainer
 colorOnSecondaryContainer
colorSecondaryVariantDEPRECATED
 colorTertiary
 colorOnTertiary
 colorTertiaryContainer
 colorOnTertiaryContainer
colorErrorcolorError
colorOnErrorcolorOnError
 colorErrorContainer
 colorOnErrorContainer
colorSurfacecolorSurface
colorOnSurfacecolorOnSurface
 colorSurfaceVariant
 colorOnSurfaceVariant
 colorSurfaceInverse
 colorOnSurfaceInverse
 colorOutline
https://material.io/blog/migrating-material-3

M2からM3への移行に際して、とりあえずビルドを通すという段階では、M3でDeprecatedになったcolorPrimaryVariantcolorSecondaryVariantを削除すればOKです。ただ、後で出てきますが、M3で追加された色の定義は、M3のコンポーネントの様々なところでデフォルト値として使われているので、最終的にはテーマの原則に従って一通りの色を定義しておくことを推奨します。

Typographyの定義の変更

Typographyの定義もM3で新しくなっています。ただしこちらは、M2の定義がほぼ1対1でM3の定義に対応しています。Typo.ktでやったように、実運用上は下の表に従って名称を変更するだけで済みます。

M2M3
textAppearanceDisplay2textAppearanceDisplayLarge
textAppearanceDisplay3textAppearanceDisplayMedium
textAppearanceHeadline1textAppearanceDisplaySmall
textAppearanceHeadline2textAppearanceHeadlineLarge
textAppearanceHeadline3textAppearanceHeadlineMedium
textAppearanceHeadline4textAppearanceHeadlineSmall
textAppearanceHeadline5textAppearanceTitleLarge
textAppearanceSubhead1/Subtitle1textAppearanceTitleMedium
textAppearanceSubhead2/Subtitle2textAppearanceTitleSmall
textAppearanceBody1textAppearanceBodyLarge
textAppearanceBody2textAppearanceBodyMedium
textAppearanceCaptiontextAppearanceBodySmall
textAppearanceButtontextAppearanceLabelLarge
textAppearanceOverlinetextAppearanceLabelMedium
N/AtextAppearanceLabelSmall
https://material.io/blog/migrating-material-3

コンポーネントをM3に切替

次に実際に使っているComposeのコンポーネントをM3に切り替えていきます。

Jetpack ComposeのUIコンポーネントを実現するコンポーザブル関数は、M2とM3で共通のシグネチャになっているものが多いです。したがって、importするパッケージを変更するだけで対応できる部分が多いです。まずはimportをM2からM3に切り替えて、エラーが出た部分をその都度確認すると効率が良いです。

以下のようにimportするパッケージを切り替えます。

import androidx.compose.material.*
import androidx.compose.material3.*

エラーが出なければこれで完了ですが、たいていはどこかにエラーが出ると思いますので、確認して対応します。

例えばMaterialTheme.colorsを参照している部分はエラーが出ます。M3ではcolorSchemeに変更します。

Surface(
    modifier = Modifier.fillMaxSize(),
    color = MaterialTheme.colors.background
) {
    ...
}
Surface(
    modifier = Modifier.fillMaxSize(),
    color = MaterialTheme.colorScheme.background
) {
    ...
}

また、Cardコンポーネントはv1.0.0-alpha06の時点ではExperimentalMaterial3Apiになっています。Cardを使うためには、Cardを呼び出している関数にOptInアノテーションを追加します。

@Composable
fun AppScreen() {
    Card() {
        ...
    }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppScreen() {
    Card() {
        ...
    }
}

ここでCard()の定義をM2とM3で比べてみます。

@Composable
@NonRestartableComposable
fun Card(
    modifier: Modifier? = Modifier,
    shape: Shape? = MaterialTheme.shapes.medium,
    backgroundColor: Color? = MaterialTheme.colors.surface,
    contentColor: Color? = contentColorFor(backgroundColor),
    border: BorderStroke? = null,
    elevation: Dp? = 1.dp,
    content: (@Composable () -> Unit)?
): Unit
@ExperimentalMaterial3Api
@Composable
fun Card(
    modifier: Modifier? = Modifier,
    interactionSource: InteractionSource? = null,
    shape: Shape? = FilledCardTokens.ContainerShape,
    containerColor: Color? = FilledCardTokens.ContainerColor.toColor(),
    contentColor: Color? = contentColorFor(containerColor),
    border: BorderStroke? = null,
    elevation: CardElevation? = CardDefaults.cardElevation(),
    content: (@Composable @ExtensionFunctionType ColumnScope.() -> Unit)?
): Unit

背景色の初期値が変更されていることが分かります。M2では背景がMaterialTheme.colors.surfacecolorSurfaceが初期値でしたが、M3ではFilledCardTokens.ContainerColor.toColor()となっています。FilledCardTokensというのが何者なのかリファレンスには説明がないですが、ソースコードを見ると最終的にはcolorSurfaceVariantが設定されていることが分かります。

internal object FilledCardTokens {
    val ContainerColor = ColorSchemeKeyTokens.SurfaceVariant
    val ContainerElevation = ElevationTokens.Level0
    val ContainerShape = ShapeTokens.CornerMedium
    val DraggedContainerElevation = ElevationTokens.Level3
    val DraggedStateLayerColor = ColorSchemeKeyTokens.OnSurface
    val FocusContainerElevation = ElevationTokens.Level0
    val FocusStateLayerColor = ColorSchemeKeyTokens.OnSurface
    val HoverContainerElevation = ElevationTokens.Level1
    val HoverStateLayerColor = ColorSchemeKeyTokens.OnSurface
    val IconColor = ColorSchemeKeyTokens.Primary
    val IconSize = 24.0.dp
    val PressedContainerElevation = ElevationTokens.Level0
    val PressedStateLayerColor = ColorSchemeKeyTokens.OnSurface
}

したがって、Cardの見た目をM2とM3で同じにするのであれば、Theme.ktcolorSurfaceVariantonColorSurfaceVariantにそれぞれcolorSurfaceonColorSurfaceと同じ色を設定すればよいことになります。

private val LightColorPalette = lightColorScheme(
    ...
    surface = Color(0xffa9f671),
    onSurface = Color(0xff005800),
    surfaceVariant = Color(0xffa9f671),
    onSurfaceVariant = Color(0xff005800),
    ...
)

また同様にFloatingActionButtonの色のデフォルトも、M2からM3で変更されています。M2の背景色はcolorSecondaryでしたが、M3ではcolorPrimaryContainerになっています。したがってM2と同じ見た目を維持するのであれば、colorPrimaryContaineronColorPrimaryContainerそれぞれに、colorSecondaryonColorSecondaryと同じ色を指定することになります。ただしこれは、”Primary”と名の付く色は同系色を使うという、マテリアルテーマの原則に反することになります。M3に移行するのであれば、カラーテーマ全体を見直して、M3の原則に則った色を指定するべきだと思います。

XMLのテーマを変更

最後に、XMLで定義しているテーマをM3に変更します。Jetpack Composeを使ったアプリの場合も、ステータスバーなどの設定のために従来のXMLのテーマが残っていますので、これを変更します。

themes.xmlstyleタグでアプリ独自のテーマを定義する際に、parentで親テーマを継承しています。これをMaterialComponentからMaterial3に変更します。themes.xmlはライトテーマ用とダークテーマ用など複数ある場合があるので、それぞれ変更します。

<style name="Theme.TestApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<style name="Theme.TestApp" parent="Theme.Material3.DayNight">

ここで注意が必要なのが、M2のDarkActionBarというテーマに対応するテーマがM3にはないということです。M2でTheme.MaterialComponents.Light.DarkActionBarTheme.MaterialComponents.DayNight.DarkActionBarを指定していた場合は、Theme.Material3.DayNightTheme.Material3.Lightを指定します。

M2とM3のテーマの対応表はGetting Startedに載っています。

M2M3
Theme.MaterialComponents.LightTheme.Material3.Light
Theme.MaterialComponents.Light.NoActionBarTheme.Material3.Light.NoActionBar
Theme.MaterialComponentsTheme.Material3.Dark
Theme.MaterialComponents.NoActionBarTheme.Material3.Dark.NoActionBar
Theme.MaterialComponents.DayNightTheme.Material3.DayNight
Theme.MaterialComponents.DayNight.NoActionBarTheme.Material3.DayNight.NoActionBar
Theme.MaterialComponents.Light.DarkActionBarN/A
Theme.MaterialComponents.DayNight.DarkActionBarN/A
https://m3.material.io/libraries/mdc-android/getting-started

Jetpack Composeを使っている場合、M3のコンポーネントに含まれるTopAppBar(SmallTopAppBarなど)を使うことになるので、バーの色はそちらで指定することになります。デフォルトではcolorPrimaryContainerが適用されます。

M3ではTopAppBarとStatusBar(時計などが表示される部分)は同じ色に設定するのがセオリーなので、themes.xml内でStatusBarの色を指定している場合は、colorPrimaryContainerと同じ色を指定します。

まとめ

以上でM2からM3への移行は完了です。といっても基本的にはデザインは現状維持のままライブラリだけを更新した状態です。本当にM3の特長を活かすには、ダイナミックカラーなどM3ならではのテクニックを使っていきたいところですが、それはまた次の機会ということで、移行の結果を見てみたいと思います。

Jetpack Compose入門(18) テーマカラーの適用」で作成したTestAppをM3に移行してみました。左がM2、中央と右がM3のスクリーンショットです。中央のスクリーンショットは、M3ですが配色はM2のときと同じになるようにしています。M3ではボタンやCardの角が丸くなっているのが分かります。右のスクリーンショットは、「Material Theme Builder」を使い、M2でPrimaryにしていたオレンジ色を使って作成したテーマカラーを適用しています。

M2
M3(配色はM2のときと同じ)
M3(テーマビルダーで作成した配色)

Material Theme Builderでは指定した色そのままではなくてちょっと鮮やかさを抑えた色がPrimaryになるので、全体的にちょっと地味になってしまいますね。ボタンではなくコンテンツを目立たせるには、これくらいのほうがいいのかもしれませんが、アプリの内容によってはもう少しアクセントの効いた色が欲しくなる気もします。この辺りは調整次第だと思います。

この記事をシェア