JetpackComposeでデザインシステムを構築する
※本記事はこちらの記事を読みやすくしたものと、ちょいプラスαした記事です。
公式ライブラリで作成する方法の記事も投稿しました。
そもそもマテリアルデザインがあるので0から構築するのはやめよう
タイトルのまんまで車輪の再開発をしてもしょうがないので、
マテリアルデザインを流用し、
必要な箇所をオーバーライドして使うのがスマートである。
実装
カラーパレットを作成して、ブランドデザインの基盤を作る。
object DlsColors
{ val primary = Color(0xFF3366FF)
val background = Color(0xFFFFFFFF)
val backgroundReverse = Color(0xFF192038)
val basic = Color(0xFF8F9BB3)
val disable = basic.copy(alpha = 0.24f)
val text = Color(0xFF192038)
val textReverse = Color(0xFFFFFFFF)
val success = Color(0xFF00E096)
val link = Color(0xFF0095FF)
val warning = Color(0xFFFFAA00)
val error = Color(0xFFFF3D71)}
カラーパレットのインターフェースを定義する。
materialColors は MaterialTheme の色をオーバーライドするために作る。
interface DlsColorPalette {
val primary: Color
val background: Color
val basic: Color
val disable: Color
val text: Color
val success: Color
val link: Color
val warning: Color
val error: Color
val materialColors: Colors}
定義したカラーパレットを実装する。
dlsLightColorPalette はデザインシステムのカラーパレットとカスタマイズされた MaterialTheme カラーを返すようになる。
fun dlsLightColorPalette(): DlsColorPalette = object : DlsColorPalette {
override val primary: Color = DlsColors.primary
override val background: Color = DlsColors.background
override val basic: Color = DlsColors.basic
override val disable: Color = DlsColors.disable
override val text: Color = DlsColors.text
override val success: Color = DlsColors.success
override val link: Color = DlsColors.link
override val warning: Color = DlsColors.warning
override val error: Color = DlsColors.error
override val materialColors: ColorPalette = lightColorPalette(
primary = DlsColors.primary
)}
MaterialTheme のカラーをオーバーライドすることは必須ではなく、
オーバーライドすることでアプリの全体にわたる色の指定が簡単にでき、
UI 実装時に色を指定するコードを減らすことができる。
ダークモード用のカラーパレットを作る
fun dlsDarkColorPalette(): DlsColorPalette = object : DlsColorPalette {
override val primary: Color = DlsColors.primary
override val background: Color = DlsColors.backgroundReverse
override val basic: Color = DlsColors.basic
override val disable: Color = DlsColors.disable
override val text: Color = DlsColors.textReverse
override val success: Color = DlsColors.success
override val link: Color = DlsColors.link
override val warning: Color = DlsColors.warning
override val error: Color = DlsColors.error
override val materialColors: Colors = darkColors(
primary = DlsColors.primary
)}
サイズ指定
デバイス画面のサイズによってサイズを変更するといった、
複数のバリエーションを持つ必要がある場合は、
カラーパレットと同様の方法で対応可能
data class DlsSize internal constructor(
val smaller: Dp = 4.dp,
val small: Dp = 8.dp,
val medium: Dp = 16.dp,
val large: Dp = 32.dp,
val larger: Dp = 64.dp
)
Typography
タイポグラフィもサイズの定義と同様に定義する。
テーマに応じてフォントを使い分ける必要がある場合、
また言語によって使用するフォントを切り替えたい場合など、
複数のフォントバリアントのサポートが必要な場合、
その場合は配色と同様に実装することで対応可能。
@Immutable
data class DlsTypography internal constructor(
val headline1: TextStyle = TextStyle(
fontSize = 36.sp,
fontWeight = FontWeight.Bold,
lineHeight = 48.sp
),
......
val button: TextStyle = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
lineHeight = 16.sp
),
val materialTypography: Typography = Typography(
body1 = paragraph1
))
materialTypography は、
materialColors と同様、MaterialTheme のフォントをオーバーライドするために用意している。
テーマ(ブランド)
MaterialTheme を利用することで、
Material Design に対応した UI を作成できる。
しかし今作成しているデザインシステムは
Material Design をベースにしたものではないため、
独自の テーマ(ブランド) を追加する必要がある。
@Composable
fun DlsTheme(
colors: DlsColorPalette = dlsLightColorPalette(),
typography: DlsTypography = DlsTypography(),
children: @Composable() () -> Unit) {
CompositionLocalProvider(
LocalDlsColors provides colors,
LocalDlsTypography provides typography,
) {
MaterialTheme(
colors = colors.materialColors,
typography = typography.materialTypography
) {
children()
}
}}
object DlsTheme {
val colors: DlsColorPalette
@Composable
@ReadOnlyComposable
get() = LocalDlsColors.current
val typography: DlsTypography
@Composable
@ReadOnlyComposable
get() = LocalDlsTypography.current
val sizes: DlsSize
@Composable
@ReadOnlyComposable
get() = DlsSize()}
internal val LocalDlsColors = staticCompositionLocalOf { dlsLightColorPalette() }
internal val LocalDlsTypography = staticCompositionLocalOf { DlsTypography() }
コンポーザブル関数 DlsTheme は、
テーマ(ブランド)によって変化する可能性のあるスタイルを外から受け取流ことができる。
今作成しているデザインシステムの場合、
配色と、タイポグラフィ を受け取ることが可能。
DlsTheme は MaterialTheme をラップしている。
Material Design の機能とスタイルを
デザインのベースとして流用することで、
デザインシステムの実装コストを大幅に減らすことができている。
CompositionLocalProviderとstaticCompositionLocalOf
CompositionLocalProvider でプロバイドしたスタイルバリュー
(サンプルでは Color とTypography)は
CompositionLocalProvider の下の階層のどこでも
CompositionLocal.current
(LocalDlsColors.current と LocalDlsTypography.current)
でアクセスすることができる。
そのバリューを変更すると CompositionLocal.current も自動的に更新される。
使い方
DlsTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = DlsTheme.colors.background
) {
Text(
text = "TEXT",
color = DlsTheme.colors.success,
style = DlsTheme.typography.paragraph1
)
}}
まず、
デザインシステムを適用したい画面を DlsTheme でラップする。
必要なスタイルは DlsTheme からアクセスでき、
MaterialTheme の使い方は変わらない。
ダークモードをスイッチ対応する場合
ダークモードなどの違うテーマをサポートするときは、
DlsTheme にスタイルを渡すことで実現可能。
val isDarkState = mutableStateOf(false)
setContent {
DlsTheme(
colors = if (isDarkState.value) dlsDarkColorPalette() else dlsLightColorPalette()
) {
Surface(
modifier = Modifier.fillMaxSize(),
color = DlsTheme.colors.background
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = if (isDarkState.value) "Is Dark" else "Is Light",
color = DlsTheme.colors.text,
style = DlsTheme.typography.paragraph1
)
Spacer(modifier = Modifier.height(DlsTheme.sizes.medium))
Button(
onClick = {
isDarkState.value = !isDarkState.value
}
) {
Text(
text = text,
style = DlsTheme.typography.button
)
}
}
}
}}
システムのダークモードに合わせて変化させる場合
@Composable
fun DlsTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
typography: DlsTypography = DlsTypography(),
children: @Composable() () -> Unit) {
val colors = if (darkTheme) dlsDarkColorPalette()
else dlsLightColorPalette()
CompositionLocalProvider(
LocalDlsColors provides colors,
LocalDlsTypography provides typography,
) {
MaterialTheme(
colors = colors.materialColors,
typography = typography.materialTypography
) {
children()
}
}}
object DlsTheme {
val colors: DlsColorPalette
@Composable
@ReadOnlyComposable
get() = LocalDlsColors.current
val typography: DlsTypography
@Composable
@ReadOnlyComposable
get() = LocalDlsTypography.current
val sizes: DlsSize
@Composable
@ReadOnlyComposable
get() = DlsSize()}
internal val LocalDlsColors = staticCompositionLocalOf { dlsLightColorPalette() }
internal val LocalDlsTypography = staticCompositionLocalOf { DlsTypography() }
参考記事
Discussion