Compose'da özel tasarım sistemleri

Material, önerdiğimiz tasarım sistemidir ve Jetpack Compose, Material'ın bir uygulamasını sunar ancak bunu kullanmak zorunda değilsiniz. Material tamamen herkese açık API'ler üzerine kurulduğundan, aynı şekilde kendi tasarım sisteminizi oluşturmanız mümkündür.

Bu konuda birkaç farklı yaklaşım izleyebilirsiniz:

Material bileşenlerini özel bir tasarım sistemiyle kullanmaya devam etmek de isteyebilirsiniz. Bunu yapmak mümkündür ancak seçtiğiniz yaklaşıma uygun olarak dikkat etmeniz gereken noktalar vardır.

MaterialTheme ve özel tasarım sistemleri tarafından kullanılan alt düzey yapılar ve API'ler hakkında daha fazla bilgi edinmek için Compose'da temanın anatomisi kılavuzuna göz atın.

Materyal Teması Oluşturma'yı genişletme

Compose Material, Materyal kurallarına uymayı kolay ve tip güvenli hale getirmek için Materyal Teması Oluşturma'yı yakından modeller. Ancak renk, tipografi ve şekil setlerini ek değerlerle genişletmek mümkündür. En basit yaklaşım, uzantı özellikleri eklemektir:

// Use with MaterialTheme.colorScheme.snackbarAction
val ColorScheme.snackbarAction: Color
    @Composable
    get() = if (isSystemInDarkTheme()) Red300 else Red700

// Use with MaterialTheme.typography.textFieldInput
val Typography.textFieldInput: TextStyle
    get() = TextStyle(/* ... */)

// Use with MaterialTheme.shapes.card
val Shapes.card: Shape
    get() = RoundedCornerShape(size = 20.dp)

Bu sayede MaterialTheme kullanım API'leriyle tutarlılık sağlanır. Compose'un kendisi tarafından tanımlanan buna bir örnek, yüksekliğe bağlı olarak kullanılması gereken yüzey rengini belirleyen surfaceColorAtElevation'dir.

Başka bir yaklaşım ise MaterialTheme ve değerlerini "saran" genişletilmiş bir tema tanımlamaktır.

Mevcut Material renklerini korurken iki renk daha eklemek istediğinizi varsayalım: caution ve yarı tehlikeli işlemler için kullanılan sarı renk olan onCaution:

@Immutable
data class ExtendedColors(
    val caution: Color,
    val onCaution: Color
)

val LocalExtendedColors = staticCompositionLocalOf {
    ExtendedColors(
        caution = Color.Unspecified,
        onCaution = Color.Unspecified
    )
}

@Composable
fun ExtendedTheme(
    /* ... */
    content: @Composable () -> Unit
) {
    val extendedColors = ExtendedColors(
        caution = Color(0xFFFFCC02),
        onCaution = Color(0xFF2C2D30)
    )
    CompositionLocalProvider(LocalExtendedColors provides extendedColors) {
        MaterialTheme(
            /* colors = ..., typography = ..., shapes = ... */
            content = content
        )
    }
}

// Use with eg. ExtendedTheme.colors.caution
object ExtendedTheme {
    val colors: ExtendedColors
        @Composable
        get() = LocalExtendedColors.current
}

Bu, MaterialTheme kullanım API'lerine benzer. Ayrıca, ExtendedTheme öğelerini MaterialTheme ile aynı şekilde iç içe yerleştirebildiğiniz için birden fazla temayı da destekler.

Material bileşenlerini kullanma

Materyal Teması Oluşturma genişletildiğinde mevcut MaterialTheme değerleri korunur ve Materyal bileşenleri makul varsayılan değerlere sahip olmaya devam eder.

Bileşenlerde genişletilmiş değerler kullanmak istiyorsanız bunları kendi composable işlevlerinizle sarmalayın, değiştirmek istediğiniz değerleri doğrudan ayarlayın ve diğerlerini kapsayıcı composable'a parametre olarak gösterin:

@Composable
fun ExtendedButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            containerColor = ExtendedTheme.colors.caution,
            contentColor = ExtendedTheme.colors.onCaution
            /* Other colors use values from MaterialTheme */
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

Ardından, uygun yerlerde Button kullanımlarını ExtendedButton ile değiştirirsiniz.

@Composable
fun ExtendedApp() {
    ExtendedTheme {
        /*...*/
        ExtendedButton(onClick = { /* ... */ }) {
            /* ... */
        }
    }
}

Material alt sistemlerini değiştirme

Materyal Teması Oluşturma'yı genişletmek yerine, sistemlerden birini veya daha fazlasını (ColorScheme, Typography veya Shapes) özel bir uygulamayla değiştirip diğerlerini olduğu gibi bırakabilirsiniz.

Renk sistemini korurken yazı tipi ve şekil sistemlerini değiştirmek istediğinizi varsayalım:

@Immutable
data class ReplacementTypography(
    val body: TextStyle,
    val title: TextStyle
)

@Immutable
data class ReplacementShapes(
    val component: Shape,
    val surface: Shape
)

val LocalReplacementTypography = staticCompositionLocalOf {
    ReplacementTypography(
        body = TextStyle.Default,
        title = TextStyle.Default
    )
}
val LocalReplacementShapes = staticCompositionLocalOf {
    ReplacementShapes(
        component = RoundedCornerShape(ZeroCornerSize),
        surface = RoundedCornerShape(ZeroCornerSize)
    )
}

@Composable
fun ReplacementTheme(
    /* ... */
    content: @Composable () -> Unit
) {
    val replacementTypography = ReplacementTypography(
        body = TextStyle(fontSize = 16.sp),
        title = TextStyle(fontSize = 32.sp)
    )
    val replacementShapes = ReplacementShapes(
        component = RoundedCornerShape(percent = 50),
        surface = RoundedCornerShape(size = 40.dp)
    )
    CompositionLocalProvider(
        LocalReplacementTypography provides replacementTypography,
        LocalReplacementShapes provides replacementShapes
    ) {
        MaterialTheme(
            /* colors = ... */
            content = content
        )
    }
}

// Use with eg. ReplacementTheme.typography.body
object ReplacementTheme {
    val typography: ReplacementTypography
        @Composable
        get() = LocalReplacementTypography.current
    val shapes: ReplacementShapes
        @Composable
        get() = LocalReplacementShapes.current
}

Material bileşenlerini kullanma

MaterialTheme sistemlerinden biri veya daha fazlası değiştirildiğinde Material bileşenlerinin olduğu gibi kullanılması, istenmeyen Material rengi, türü veya şekil değerlerine neden olabilir.

Bileşenlerde yedek değerler kullanmak istiyorsanız bunları kendi composable işlevlerinizle sarmalayın, ilgili sistemin değerlerini doğrudan ayarlayın ve diğerlerini, içeren composable'a parametre olarak gösterin.

@Composable
fun ReplacementButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        shape = ReplacementTheme.shapes.component,
        onClick = onClick,
        modifier = modifier,
        content = {
            ProvideTextStyle(
                value = ReplacementTheme.typography.body
            ) {
                content()
            }
        }
    )
}

Ardından, uygun yerlerde Button kullanımlarını ReplacementButton ile değiştirirsiniz.

@Composable
fun ReplacementApp() {
    ReplacementTheme {
        /*...*/
        ReplacementButton(onClick = { /* ... */ }) {
            /* ... */
        }
    }
}

Tamamen özel bir tasarım sistemi uygulama

Materyal Teması Oluşturma'yı tamamen özel bir tasarım sistemiyle değiştirmek isteyebilirsiniz. MaterialTheme aşağıdaki sistemleri sağlar:

  • ColorScheme, Typography ve Shapes: Materyal Teması Oluşturma sistemleri
  • TextSelectionColors: Text ve TextField tarafından metin seçimi için kullanılan renkler
  • ripple, LocalIndication olarak sağlanır: Indication için materyal uygulaması

Material bileşenlerini kullanmaya devam etmek istiyorsanız özel temalarınızda bu sistemlerden bazılarını değiştirmeniz veya istenmeyen davranışları önlemek için bileşenlerinizdeki sistemleri yönetmeniz gerekir.

Ancak tasarım sistemleri, Material'ın dayandığı kavramlarla sınırlı değildir. Mevcut sistemleri değiştirebilir ve diğer kavramları temalarla uyumlu hale getirmek için yeni sınıflar ve türler içeren tamamen yeni sistemler sunabilirsiniz.

Aşağıdaki kodda, gradyanlar (List<Color>) içeren özel renkler sistemi modelliyor, bir tür sistemi ekliyor, yeni bir yükseklik sistemi sunuyor ve MaterialTheme tarafından sağlanan diğer sistemleri hariç tutuyoruz:

Renkler için gradyanlar, özel tipografi ve yükseklik kullanan öğeler içeren özel bir tasarım sistemini gösteren mobil uygulama kullanıcı arayüzünün ekran görüntüsü.

@Immutable
data class CustomColors(
    val content: Color,
    val component: Color,
    val background: List<Color>
)

@Immutable
data class CustomTypography(
    val body: TextStyle,
    val title: TextStyle
)

@Immutable
data class CustomElevation(
    val default: Dp,
    val pressed: Dp
)

val LocalCustomColors = staticCompositionLocalOf {
    CustomColors(
        content = Color.Unspecified,
        component = Color.Unspecified,
        background = emptyList()
    )
}
val LocalCustomTypography = staticCompositionLocalOf {
    CustomTypography(
        body = TextStyle.Default,
        title = TextStyle.Default
    )
}
val LocalCustomElevation = staticCompositionLocalOf {
    CustomElevation(
        default = Dp.Unspecified,
        pressed = Dp.Unspecified
    )
}

@Composable
fun CustomTheme(
    /* ... */
    content: @Composable () -> Unit
) {
    val customColors = CustomColors(
        content = Color(0xFFDD0D3C),
        component = Color(0xFFC20029),
        background = listOf(Color.White, Color(0xFFF8BBD0))
    )
    val customTypography = CustomTypography(
        body = TextStyle(fontSize = 16.sp),
        title = TextStyle(fontSize = 32.sp)
    )
    val customElevation = CustomElevation(
        default = 4.dp,
        pressed = 8.dp
    )
    CompositionLocalProvider(
        LocalCustomColors provides customColors,
        LocalCustomTypography provides customTypography,
        LocalCustomElevation provides customElevation,
        content = content
    )
}

// Use with eg. CustomTheme.elevation.small
object CustomTheme {
    val colors: CustomColors
        @Composable
        get() = LocalCustomColors.current
    val typography: CustomTypography
        @Composable
        get() = LocalCustomTypography.current
    val elevation: CustomElevation
        @Composable
        get() = LocalCustomElevation.current
}

Material bileşenlerini kullanma

MaterialTheme yoksa Material bileşenlerini olduğu gibi kullanmak, istenmeyen Material rengi, türü ve şekil değerleri ile gösterge davranışına neden olur.

Bileşenlerde özel değerler kullanmak istiyorsanız bunları kendi composable işlevlerinize sarmalayın, ilgili sistemin değerlerini doğrudan ayarlayın ve diğerlerini kapsayıcı composable'a parametre olarak gösterin.

Özel temanızdan ayarladığınız değerlere erişmenizi öneririz. Alternatif olarak, temanız Color, TextStyle, Shape veya diğer sistemleri sağlamıyorsa bunları sabit kodlayabilirsiniz.

@Composable
fun CustomButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            containerColor = CustomTheme.colors.component,
            contentColor = CustomTheme.colors.content,
            disabledContainerColor = CustomTheme.colors.content
                .copy(alpha = 0.12f)
                .compositeOver(CustomTheme.colors.component),
            disabledContentColor = CustomTheme.colors.content
                .copy(alpha = 0.38f)

        ),
        shape = ButtonShape,
        elevation = ButtonDefaults.elevatedButtonElevation(
            defaultElevation = CustomTheme.elevation.default,
            pressedElevation = CustomTheme.elevation.pressed
            /* disabledElevation = 0.dp */
        ),
        onClick = onClick,
        modifier = modifier,
        content = {
            ProvideTextStyle(
                value = CustomTheme.typography.body
            ) {
                content()
            }
        }
    )
}

val ButtonShape = RoundedCornerShape(percent = 50)

Gradyanları temsil etmek için List<Color> gibi yeni sınıf türleri eklediyseniz bileşenleri sarmalamak yerine sıfırdan uygulamak daha iyi olabilir. Örnek için Jetsnack örneğindeki JetsnackButton dosyasına göz atın.