Tema dengan Gaya

Ada beberapa cara untuk membuat aplikasi menggunakan Gaya. Pilihan Anda bergantung pada posisi aplikasi Anda dalam kaitannya dengan penerapan Desain Material:

  1. Sistem desain yang sepenuhnya kustom, tidak menggunakan Desain Material
    • Rekomendasi: Tentukan gaya komponen yang menggunakan nilai dari tema, dan tampilkan parameter gaya pada komponen sistem desain.
  2. Menggunakan Desain Material
    • Rekomendasi: Tunggu penerapan Material untuk berintegrasi dengan Gaya. Gunakan gaya pada komponen Anda sendiri jika memungkinkan.

Lapisan Gaya

Dalam model Compose tradisional, penyesuaian sering kali sangat bergantung pada penggantian token global (warna dan tipografi) yang disediakan oleh MaterialTheme, atau menggabungkan dan mengganti properti composable sistem desain jika memungkinkan. Terkadang, ada properti dalam lapisan Material yang tidak ditampilkan melalui subsistem atau parameter, tetapi merupakan default hardcode pada komponen itu sendiri.

Dengan Styles API, ada lapisan abstraksi baru yang menjadi jembatan antara subsistem dan komponen: Gaya.

Lapisan Tanggung jawab Contoh
Nilai subsistem Nilai bernama val Primary = Color(0xFF34A85E)
Gaya Atom Gaya yang hanya melakukan satu perubahan properti val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic
Gaya Komponen Konfigurasi khusus komponen Tombol dengan latar belakang Utama dan padding 16 dp. val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }
Komponen Elemen UI fungsional yang menggunakan Gaya. Button(style = buttonStyle) { ... }
Diagram yang menunjukkan Tema dengan Gaya dengan pengenalan lapisan baru
Gambar 1. Contoh komponen dan cara mengakses gaya dari tema.

Gaya atom versus monolitik

Dengan Styles API, Anda dapat membagi Gaya menjadi gaya atom terpisah. Selain menentukan gaya khusus komponen yang kompleks seperti baseButtonStyle, Anda juga dapat membuat gaya utilitas kecil dan sekali pakai. Gaya ini bertindak sebagai "atom" Anda.

// Define single-purpose "atomic" styles
val paddingAtomic = Style {
    contentPadding(16.dp)
}
val roundedCornerShapeAtomic = Style {
    shape(RoundedCornerShape(8.dp))
}
val primaryBackgroundAtomic = Style {
    background(Color.Blue)
}
val largeSizeAtomic = Style {
    size(100.dp, 40.dp)
}
val interactiveShadowAtomic = Style {
    hovered {
        animate {
            dropShadow(
                Shadow(
                    offset = DpOffset(
                        0.dp,
                        0.dp
                    ),
                    radius = 2.dp,
                    spread = 0.dp,
                    color = Color.Blue,
                )
            )
        }
    }
}

Komposisi menggunakan "then"

Salah satu fitur canggih dari Styles API baru adalah operator then, yang memungkinkan Anda menggabungkan beberapa objek Style. Hal ini memungkinkan Anda membuat komponen menggunakan class utilitas atom.

Tradisional (non-atom):

// One large monolithic style
val buttonStyle = Style {
    contentPadding(16.dp)
    shape(RoundedCornerShape(8.dp))
    background(Color.Blue)
}

Refaktor atom:

// Combine atoms to create the final appearance
val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then interactiveShadowAtomic

Menggunakan Gaya dalam sistem desain Anda

Pertimbangkan opsi berikut saat menggunakan Gaya dalam sistem desain Anda, bergantung pada posisi sistem desain Anda dalam spektrum.

Sistem desain kustom dengan Gaya

Pertimbangkan kapan: Anda telah menerima panduan merek yang luas dan tidak didasarkan pada Desain Material, dan Anda tidak berencana menggunakan Desain Material.

Strategi: Terapkan sistem desain yang sepenuhnya kustom, dan tampilkan gaya sebagai bagian dari tema.

Opsi ini adalah jalur kustom jika Anda tidak menggunakan Material sebagai bahasa sistem desain utama. Anda sepenuhnya melewati MaterialTheme untuk definisi visual dan telah membuat tema kustom Anda sendiri. Anda membuat CompanyTheme yang bertindak sebagai penampung untuk Gaya Anda.

  • Cara kerjanya: Buat objek CompanyTheme yang menyimpan objek Style untuk setiap komponen dalam sistem Anda. Komponen Anda (baik wrapper di sekitar logika Material atau implementasi Box atau Layout kustom) menggunakan gaya ini secara langsung, dan menampilkan parameter Style untuk konsumen sistem desain Anda.
  • Lapisan Gaya: Gaya adalah definisi utama sistem desain Anda. Token adalah variabel bernama yang dimasukkan ke dalam gaya ini. Hal ini memungkinkan penyesuaian mendalam, seperti menentukan animasi unik untuk perubahan status (misalnya, menganimasikan skala dan warna saat ditekan).

Jika Anda membuat tema kustom sendiri tanpa menggunakan Material, dan ingin menggunakan gaya, tambahkan daftar gaya ke Tema Anda. Hal ini memungkinkan Anda mengakses gaya dasar dari mana saja di project Anda.

  1. Buat class Styles yang menyimpan berbagai gaya di aplikasi Anda dan buat default. Misalnya, di aplikasi Jetsnack, class diberi nama JetsnackStyles:

    object JetsnackStyles{
        val buttonStyle: Style = Style {
            shape(shapes.medium)
            background(colors.brand)
            contentColor(colors.textPrimary)
            contentPaddingVertical(8.dp)
            contentPaddingHorizontal(24.dp)
            textStyle(typography.labelLarge)
            disabled {
                animate {
                    background(colors.brandSecondary)
                }
            }
        }
        val cardStyle: Style = Style {
            shape(shapes.medium)
            background(colors.uiBackground)
            contentColor(colors.textPrimary)
        }
    }

  2. Berikan Styles sebagai bagian dari tema keseluruhan Anda, dan tampilkan fungsi ekstensi helper di StyleScope untuk mengakses subsistem:

    @Immutable
    class JetsnackTheme(
        val colors: JetsnackColors = LightJetsnackColors,
        val typography: androidx.compose.material3.Typography = androidx.compose.material3.Typography(),
        val shapes: Shapes = Shapes()
    ) {
        companion object {
            val colors: JetsnackColors
                @Composable @ReadOnlyComposable
                get() = LocalJetsnackTheme.current.colors
    
            val typography: androidx.compose.material3.Typography
                @Composable @ReadOnlyComposable
                get() = LocalJetsnackTheme.current.typography
    
            val shapes: Shapes
                @Composable @ReadOnlyComposable
                get() = LocalJetsnackTheme.current.shapes
    
            val styles: JetsnackStyles = JetsnackStyles
    
            val LocalJetsnackTheme: ProvidableCompositionLocal<JetsnackTheme>
                get() = LocalJetsnackThemeInstance
        }
    }
    
    val StyleScope.colors: JetsnackColors
        get() = LocalJetsnackTheme.currentValue.colors
    
    val StyleScope.typography: androidx.compose.material3.Typography
        get() = LocalJetsnackTheme.currentValue.typography
    
    val StyleScope.shapes: Shapes
        get() = LocalJetsnackTheme.currentValue.shapes
    
    internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackTheme() }
    
    @Composable
    fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
        val colors = if (darkTheme) DarkJetsnackColors else LightJetsnackColors
        val theme = JetsnackTheme(colors = colors)
    
        CompositionLocalProvider(
            LocalJetsnackTheme provides theme,
        ) {
            MaterialTheme(
                typography = LocalJetsnackTheme.current.typography,
                shapes = LocalJetsnackTheme.current.shapes,
                content = content,
            )
        }
    }

  3. Akses JetsnackStyles dalam composable Anda:

    @Composable
    fun CustomButton(modifier: Modifier,
                     style: Style = Style,
                     text: String) {
        val interactionSource = remember { MutableInteractionSource() }
        val styleState = remember(interactionSource) { MutableStyleState(interactionSource) }
    
        // Apply style to top level container in combination with incoming style from parameter.
        Box(modifier = modifier
            .clickable(
                interactionSource = interactionSource,
                indication = null,
                enabled = true,
                role = Role.Button,
                onClick = {
    
                },
            )
            .styleable(styleState, JetsnackTheme.styles.buttonStyle, style)) {
            Text(text)
        }
    }

Selain penggunaan tema global, ada strategi alternatif untuk menggabungkan Styles ke dalam aplikasi Anda. Anda dapat memanfaatkan Styles secara inline untuk situs panggilan tertentu atau menggunakan definisi statis jika kemampuan tema penuh tidak diperlukan. Styles tidak boleh ditukar secara kondisional kecuali jika seluruh gaya pada dasarnya berbeda. Anda sebaiknya mengakses token dinamis di dalam definisi visual daripada beralih di antara objek gaya yang berbeda.