この記事では、Jetpack Composeを使ったアプリをMaterial Design 2からMaterial Design 3へ移行する方法を確認します。「Jetpack Compose入門(18) テーマカラーの適用」で作成したTestAppをMaterial Design 3に対応させていきます。
以降、この記事内ではMaterial Design 2と3をそれぞれ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()
関数はM2とM3で引数が異なるので、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が廃止になっています。
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 |
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 |
コンポーネントを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.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 |
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にしていたオレンジ色を使って作成したテーマカラーを適用しています。
Material Theme Builderでは指定した色そのままではなくてちょっと鮮やかさを抑えた色がPrimaryになるので、全体的にちょっと地味になってしまいますね。ボタンではなくコンテンツを目立たせるには、これくらいのほうがいいのかもしれませんが、アプリの内容によってはもう少しアクセントの効いた色が欲しくなる気もします。この辺りは調整次第だと思います。