Jetpack Compose でアプリのテーマ切り替えを実装してみた
はじめに
こちらが完成形です。
- テーマ切り替えの実装
- 端末のダークモードON/OFFも検知
プロジェクトを作成する
Jetpack Compose で作っていくので、
新規プロジェクトから Empty Compose Activity を作成します。
[パッケージ名].ui.theme
パッケージの下にColor.kt
やTheme.kt
があることを確認します。
Color.kt の編集
Color.kt
を以下のように書き換え、カラーを定義します。
※ 色を決めるのが楽し過ぎて、結果的にここが一番時間かかる
package com.example.sampleapp.ui.theme
import androidx.compose.ui.graphics.Color
object LoidColors {
val basic = Color(0xFF817244)
val highlight = Color(0xFFE9CC78)
val dark = Color(0xFF323A1E)
}
object YorColors {
val basic = Color(0xFF440000)
val highlight = Color(0xFFD63C3C)
val dark = Color(0xFF200000)
}
object AnyaColors {
val basic = Color(0xFFDD9389)
val highlight = Color(0xFFC5EE8E)
val dark = Color(0xFF331713)
}
Theme.kt の編集
カラーパレットを作る
ColorPalette という enum を作ります。
enum class ColorPalette(
val basic: Color,
val highlight: Color,
val dark: Color
) {}
テーマカラーを設定する
ColorPalette
に、ロイドさん/ヨルさん/アーニャ の種別を追加します。
enum class ColorPalette(
val basic: Color,
val highlight: Color,
val dark: Color
) {
LOID(
LoidColors.basic,
LoidColors.highlight,
LoidColors.dark
),
YOR(
YorColors.basic,
YorColors.highlight,
YorColors.dark
),
ANYA(
AnyaColors.basic,
AnyaColors.highlight,
AnyaColors.dark
);
}
名前とアイコンが取得できるようにする
drawable
に使いたい画像を追加しています。
isSystemInDarkTheme()
で端末のダークモードON/OFFがわかります。
enum class ColorPalette(
val basic: Color,
val highlight: Color,
val dark: Color
) {
// これを追加する
companion object {
@Composable
fun ColorPalette.toName(): String {
return when (this) {
LOID -> "Loid Forger(Twilight)"
YOR -> "Yor Forger(Briar Rose)"
ANYA -> "Anya Forger(Subject 007)"
}
}
@Composable
fun ColorPalette.toPainter(): Painter {
return if (isSystemInDarkTheme()) {
when (this) {
LOID -> painterResource(id = R.drawable.loiddark)
YOR -> painterResource(id = R.drawable.yordark)
ANYA -> painterResource(id = R.drawable.anyadark)
}
} else {
when (this) {
LOID -> painterResource(id = R.drawable.loidlight)
YOR -> painterResource(id = R.drawable.yorlight)
ANYA -> painterResource(id = R.drawable.anyalight)
}
}
}
}
}
テーマの切り替えを受けてアプリ全体で参照可能にする
ColorPalette
の CompositionLocal
を作成します。
CompositionLocal
がわからない方は調べてみてください。
Composable 同士で値や状態を反映させる時に、引数バケツリレー状態になりがちですが、
テーマカラーのように、ある Composable の配下全てで同じ値を参照したい時に使われます。
internal val LocalColorPalette = staticCompositionLocalOf { ColorPalette.LOID }
他の Composable から、SampleAppTheme.current
という形式で参照できるようにします。
object SampleAppTheme {
val current: ColorPalette
@Composable
@ReadOnlyComposable
get() = LocalColorPalette.current
}
SampleAppTheme は カラーパレットとダークテーマの切り替えを契機に再コンポーズするようにします。
まずステータスバーやナビゲーションバーの色が変わるようにして、
CompositionLocalProvider() に最新のカラーパレットを渡して反映します。
@Composable
fun SampleAppTheme(
colorPalette: ColorPalette,
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val sysUiController = rememberSystemUiController()
LaunchedEffect(colorPalette, darkTheme) {
val color = if (darkTheme) {
colorPalette.dark
} else {
colorPalette.highlight
}
sysUiController.setSystemBarsColor(color)
}
CompositionLocalProvider(LocalColorPalette provides colorPalette) {
MaterialTheme(
colors = lightColors(),
typography = Typography,
shapes = Shapes,
content = content
)
}
}
※ accompanist-systemuicontroller を入れています。
UIの実装
テーマをセット&参照
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var themeColor by remember {
mutableStateOf(ColorPalette.LOID)
}
SampleAppTheme(themeColor) {
Surface(modifier = Modifier.fillMaxSize(), color = SampleAppTheme.current.basic) {
}
}
}
}
}
現在選択されているテーマの値は、SampleAppTheme.current.basic
といった形で取得できます。
var themeColor
に別のテーマを入れたら再コンポーズされてテーマが反映されます。
ボタンが押された時にでも themeColor = ColorPalette.HOGE
としてやりましょう。
アイコンの切り替え
先ほど作成したtoPainter()
でテーマの画像を取得できます。
current が変われば勝手に切り替わります。
Image(painter = SampleAppTheme.current.toPainter(), contentDescription = "")
テキストの切り替え
これも同じ感じ。
Text(
text = SampleAppTheme.current.toName(),
color = SampleAppTheme.current.highlight
)
できた
GIFだと荒くてわかりにくいので画像置いておきます。
UIは適当に配置してるだけなのに、色があるだけでなんだか良さげ。
ライトモード
ライトモード
ライトモード
ダークモード
ダークモード
ダークモード
テーマ選択
終わりに
サンプルアプリのアイコンはこちらからお借りいたしました。
犬派なので推しはボンドです。
アプリを閉じてもテーマ設定を維持したい場合は、
ローカルDBにても値をキャッシュしておいて取り出せばいいので簡単です。
GitHub にコード置いてますのでぜひ動かしてみてください。
Discussion