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.material**3**.MaterialThemeに変更します。“3”をつけるだけです。
テーマを設定するMaterialTheme()関数はM2とM3で引数が異なるので、M3の形式に合わせて変更します。
@Composablefun MaterialTheme( colors: Colors? = MaterialTheme.colors, typography: Typography? = MaterialTheme.typography, shapes: Shapes? = MaterialTheme.shapes, content: (@Composable () -> Unit)?): Unit@Composablefun MaterialTheme( colorScheme: ColorScheme? = MaterialTheme.colorScheme, typography: Typography? = MaterialTheme.typography, content: (@Composable () -> Unit)?): UnitM3では第一引数が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.MaterialThemeimport androidx.compose.material.darkColorsimport androidx.compose.material.lightColors
private val DarkColorPalette = darkColors( primary = Purple200, primaryVariant = Purple700, secondary = Teal200)
private val LightColorPalette = lightColors( ...)
@Composablefun 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.MaterialThemeimport androidx.compose.material3.darkColorSchemeimport androidx.compose.material3.lightColorScheme
private val DarkColorPalette = darkColorScheme( primary = Purple200,// primaryVariant = Purple700, secondary = Teal200)
private val LightColorPalette = lightColorScheme( ...)
@Composablefun 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が廃止になっています。
| M2 | M3 |
|---|---|
| android:backgroundColor | android:backgroundColor |
| colorPrimary | colorPrimary |
| colorOnPrimary | colorOnPrimary |
| colorPrimaryContainer | |
| colorOnPrimaryContainer | |
| colorPrimaryInverse | |
| colorPrimaryVariant | DEPRECATED |
| colorPrimarySurface | colorSurface |
| colorOnPrimarySurface | colorOnSurface |
| colorSecondary | colorSecondary |
| colorOnSecondary | colorOnSecondary |
| colorSecondaryContainer | |
| colorOnSecondaryContainer | |
| colorSecondaryVariant | DEPRECATED |
| colorTertiary | |
| colorOnTertiary | |
| colorTertiaryContainer | |
| colorOnTertiaryContainer | |
| colorError | colorError |
| colorOnError | colorOnError |
| colorErrorContainer | |
| colorOnErrorContainer | |
| colorSurface | colorSurface |
| colorOnSurface | colorOnSurface |
| colorSurfaceVariant | |
| colorOnSurfaceVariant | |
| colorSurfaceInverse | |
| colorOnSurfaceInverse | |
| colorOutline |
https://material.io/blog/migrating-material-3
M2からM3への移行に際して、とりあえずビルドを通すという段階では、M3でDeprecatedになったcolorPrimaryVariantとcolorSecondaryVariantを削除すればOKです。ただ、後で出てきますが、M3で追加された色の定義は、M3のコンポーネントの様々なところでデフォルト値として使われているので、最終的にはテーマの原則に従って一通りの色を定義しておくことを推奨します。
Typographyの定義の変更


Typographyの定義もM3で新しくなっています。ただしこちらは、M2の定義がほぼ1対1でM3の定義に対応しています。Typo.ktでやったように、実運用上は下の表に従って名称を変更するだけで済みます。
| M2 | M3 |
|---|---|
| textAppearanceDisplay2 | textAppearanceDisplayLarge |
| textAppearanceDisplay3 | textAppearanceDisplayMedium |
| textAppearanceHeadline1 | textAppearanceDisplaySmall |
| textAppearanceHeadline2 | textAppearanceHeadlineLarge |
| textAppearanceHeadline3 | textAppearanceHeadlineMedium |
| textAppearanceHeadline4 | textAppearanceHeadlineSmall |
| textAppearanceHeadline5 | textAppearanceTitleLarge |
| textAppearanceSubhead1/Subtitle1 | textAppearanceTitleMedium |
| textAppearanceSubhead2/Subtitle2 | textAppearanceTitleSmall |
| textAppearanceBody1 | textAppearanceBodyLarge |
| textAppearanceBody2 | textAppearanceBodyMedium |
| textAppearanceCaption | textAppearanceBodySmall |
| textAppearanceButton | textAppearanceLabelLarge |
| textAppearanceOverline | textAppearanceLabelMedium |
| N/A | textAppearanceLabelSmall |
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アノテーションを追加します。
@Composablefun AppScreen() { Card() { ... }}@OptIn(ExperimentalMaterial3Api::class)@Composablefun AppScreen() { Card() { ... }}ここでCard()の定義をM2とM3で比べてみます。
@Composable@NonRestartableComposablefun 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@Composablefun 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.ktでcolorSurfaceVariantとonColorSurfaceVariantにそれぞれcolorSurfaceとonColorSurfaceと同じ色を設定すればよいことになります。
private val LightColorPalette = lightColorScheme( ... surface = Color(0xffa9f671), onSurface = Color(0xff005800), surfaceVariant = Color(0xffa9f671), onSurfaceVariant = Color(0xff005800), ...)また同様にFloatingActionButtonの色のデフォルトも、M2からM3で変更されています。M2の背景色はcolorSecondaryでしたが、M3ではcolorPrimaryContainerになっています。したがってM2と同じ見た目を維持するのであれば、colorPrimaryContainerとonColorPrimaryContainerそれぞれに、colorSecondaryとonColorSecondaryと同じ色を指定することになります。ただしこれは、“Primary”と名の付く色は同系色を使うという、マテリアルテーマの原則に反することになります。M3に移行するのであれば、カラーテーマ全体を見直して、M3の原則に則った色を指定するべきだと思います。
XMLのテーマを変更
最後に、XMLで定義しているテーマをM3に変更します。Jetpack Composeを使ったアプリの場合も、ステータスバーなどの設定のために従来のXMLのテーマが残っていますので、これを変更します。
themes.xmlのstyleタグでアプリ独自のテーマを定義する際に、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.DarkActionBarやTheme.MaterialComponents.DayNight.DarkActionBarを指定していた場合は、Theme.Material3.DayNightやTheme.Material3.Lightを指定します。
M2とM3のテーマの対応表はGetting Startedに載っています。
| M2 | M3 |
|---|---|
| Theme.MaterialComponents.Light | Theme.Material3.Light |
| Theme.MaterialComponents.Light.NoActionBar | Theme.Material3.Light.NoActionBar |
| Theme.MaterialComponents | Theme.Material3.Dark |
| Theme.MaterialComponents.NoActionBar | Theme.Material3.Dark.NoActionBar |
| Theme.MaterialComponents.DayNight | Theme.Material3.DayNight |
| Theme.MaterialComponents.DayNight.NoActionBar | Theme.Material3.DayNight.NoActionBar |
| Theme.MaterialComponents.Light.DarkActionBar | N/A |
| Theme.MaterialComponents.DayNight.DarkActionBar | N/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になるので、全体的にちょっと地味になってしまいますね。ボタンではなくコンテンツを目立たせるには、これくらいのほうがいいのかもしれませんが、アプリの内容によってはもう少しアクセントの効いた色が欲しくなる気もします。この辺りは調整次第だと思います。