Compose'da özel tasarım sistemleri

Material, önerdiğimiz tasarım sistemi olsa ve Jetpack Compose, Material'ın bir uygulamasını içerse de bu sistemi kullanmak zorunda değilsiniz. Material tamamen herkese açık API'ler üzerine kurulduğundan aynı şekilde kendi tasarım sisteminizi oluşturabilirsiniz.

Bu konuda birkaç yaklaşım izleyebilirsiniz:

Ayrıca, özel tasarım sistemine sahip Material bileşenlerini kullanmaya devam etmek isteyebilirsiniz. Bu işlemi yapmanız mümkündür ancak seçtiğiniz yaklaşıma uygun olması için dikkat etmeniz gereken noktalar vardır.

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

Materyal temasını genişletme

Compose Material, Material Temalandırma'yı yakından örnek alarak Material kurallarına uymayı basit ve tür açısından güvenli hale getirir. Ancak renk, tipografi ve şekil kümelerini 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, MaterialTheme kullanım API'leriyle tutarlılık sağlar. Compose'un kendisi tarafından tanımlanan bu duruma bir örnek, yüksekliğe bağlı olarak kullanılması gereken yüzey rengini belirleyen surfaceColorAtElevation'dir.

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

Mevcut Material renklerini korurken caution ve onCaution olmak üzere iki renk daha eklemek istediğinizi varsayalım. caution, yarı tehlikeli işlemler için kullanılan sarı bir renktir.

@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 öğeleriyle aynı şekilde iç içe yerleştirebileceğiniz için birden fazla temayı da destekler.

Material bileşenlerini kullanma

Material Teması'nı genişletirken mevcut MaterialTheme değerleri korunur ve Material bileşenleri yine de makul varsayılan değerlere sahip olur.

Bileşenlerde genişletilmiş değerler kullanmak istiyorsanız bunları kendi composable işlevlerinize sarmalayın, değiştirmek istediğiniz değerleri doğrudan ayarlayın ve diğerlerini kapsayan composable'a parametre olarak sunun:

@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 = { /* ... */ }) {
            /* ... */
        }
    }
}

Malzeme alt sistemlerini değiştirme

Material Theming'i genişletmek yerine, diğerlerini korurken Colors, Typography veya Shapes gibi bir veya daha fazla sistemi özel bir uygulamayla değiştirmek isteyebilirsiniz.

Renk sistemini korurken tür 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şenlerini olduğu gibi kullanmak istenmeyen Material rengi, türü veya şekil değerlerine neden olabilir.

Bileşenlerde değiştirme değerleri kullanmak istiyorsanız bunları kendi composable işlevlerinize sarmalayın, ilgili sistemin değerlerini doğrudan ayarlayın ve diğerlerini kapsayan composable'a parametre olarak sunun.

@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

Material Theming'i tamamen özel bir tasarım sistemiyle değiştirmek isteyebilirsiniz. MaterialTheme'nın aşağıdaki sistemleri sağladığını düşünün:

  • Colors, Typography ve Shapes: Materyal Teması Oluşturma sistemleri
  • TextSelectionColors: Text ve TextField tarafından metin seçimi için kullanılan renkler
  • Ripple ve RippleTheme: Indication'ın materyal olarak uygulanması

Material bileşenlerini kullanmaya devam etmek istiyorsanız özel temanızda veya temalarınızda bu sistemlerden bazılarını değiştirmeniz ya da 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. Diğer kavramları temalarla uyumlu hale getirmek için mevcut sistemleri değiştirebilir ve tamamen yeni sistemler (yeni sınıflar ve türlerle) oluşturabilirsiniz.

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

@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 bulunmadığında, Material bileşenlerinin olduğu gibi kullanılması istenmeyen Material rengi, türü ve şekil değerlerine ve 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 içeren composable'a parametre olarak sunun.

Belirlediğiniz değerlere özel temanızdan 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)

List<Color> gibi yeni sınıf türleri eklediyseniz bunları sarmalamak yerine bileşenleri sıfırdan uygulamak daha iyi olabilir. Örnek olarak, Jetsnack örneğindeki JetsnackButton öğesine göz atın.