Compose'da özel tasarım sistemleri

Önerdiğimiz tasarım sistemimiz Material'ı ve Jetpack Compose'u Material uygulamasını sunuyor. Ancak bu hizmeti kullanmaya zorlamak zorunda değilsiniz. Materyaller tamamen herkese açık API'ler üzerine kurulu olduğundan aynı şekilde kendi tasarım sisteminizi de oluşturabilirsiniz.

Uygulayabileceğiniz çeşitli yaklaşımlar vardır:

Materyal bileşenlerini özel bir tasarım sistemiyle kullanmaya devam etmek de isteyebilirsiniz. Bunu yapmak da mümkündür, ancak uyguladığınız yaklaşıma uygun olması için unutmamanız gereken şeyler var.

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.

Malzeme Temasını Uzatma

Compose Material, Materyal yönergelerine uymayı basit ve tür açısından güvenli hale getirmek için Materyal Tema modelini yakın bir şekilde modeller. Bununla birlikte, renk, yazı tipi ve şekil gruplarını ek değerlerle genişletmek mümkündür.

En basit yaklaşım, uzantı özellikleri eklemektir:

// Use with MaterialTheme.colors.snackbarAction
val Colors.snackbarAction: Color
    get() = if (isLight) 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 bir örnek primarySurface, Colors.isLight bağlı olarak primary ile surface arasında bir proxy görevi görür.

Diğer bir yaklaşım, MaterialTheme öğesini ve değerlerini "sarmalayan" genişletilmiş bir tema tanımlamaktır.

Mevcut Malzeme renklerini korurken iki ek renk (tertiary ve onTertiary) eklemek istediğinizi varsayalım:

@Immutable
data class ExtendedColors(
    val tertiary: Color,
    val onTertiary: Color
)

val LocalExtendedColors = staticCompositionLocalOf {
    ExtendedColors(
        tertiary = Color.Unspecified,
        onTertiary = Color.Unspecified
    )
}

@Composable
fun ExtendedTheme(
    /* ... */
    content: @Composable () -> Unit
) {
    val extendedColors = ExtendedColors(
        tertiary = Color(0xFFA8EFF0),
        onTertiary = Color(0xFF002021)
    )
    CompositionLocalProvider(LocalExtendedColors provides extendedColors) {
        MaterialTheme(
            /* colors = ..., typography = ..., shapes = ... */
            content = content
        )
    }
}

// Use with eg. ExtendedTheme.colors.tertiary
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ştirebileceğiniz için birden fazla temayı destekler.

Materyal bileşenlerini kullanma

Malzeme Teması'nın kapsamı genişletilirken, mevcut MaterialTheme değerleri korunur ve Materyal bileşenleri yine de makul varsayılanlara sahip olur.

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

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

Daha sonra, uygun durumlarda Button kullanımlarını ExtendedButton ile değiştirirsiniz.

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

Material sistemlerini değiştirme

Materyal Temayı genişletmek yerine, bir veya daha fazla sistemi (Colors, Typography veya Shapes) özel bir uygulamayla değiştirirken diğerlerini de koruyabilirsiniz.

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
}

Materyal bileşenlerini kullanma

Bir veya daha fazla MaterialTheme sistemi değiştirildiğinde, Malzeme bileşenlerinin olduğu gibi kullanılması istenmeyen Malzeme rengi, tür veya şekil değerlerine yol açabilir.

Bileşenlerde değiştirme değerleri kullanmak istiyorsanız bunları kendi composable işlevlerinize sarmalayın, ilgili sistem için değerleri doğrudan ayarlayın ve diğerlerini, kapsayıcı 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()
            }
        }
    )
}

Daha sonra, uygun durumlarda Button kullanımlarını ReplacementButton ile değiştirirsiniz.

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

Tamamen özel bir tasarım sistemini uygulamaya geçirme

Materyal Temaları'nı tamamen özel bir tasarım sistemiyle değiştirmek isteyebilirsiniz. MaterialTheme kapsamında aşağıdaki sistemlerin sunulduğunu göz önünde bulundurun:

  • Colors, Typography ve Shapes: Malzeme teması sistemleri
  • ContentAlpha: Text ve Icon'da vurguyu göstermek için opaklık seviyeleri
  • TextSelectionColors: Text ve TextField tarafından metin seçimi için kullanılan renkler
  • Ripple ve RippleTheme: Indication öğesinin materyal uygulaması

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

Ne var ki, tasarım sistemleri Materyal’in dayandığı kavramlarla sınırlı değildir. Diğer kavramları temalarla uyumlu hale getirmek için mevcut sistemleri değiştirebilir ve yeni sınıflar ve türlerle yepyeni sistemler kullanabilirsiniz.

Aşağıdaki kodda, renk geçişleri (List<Color>) içeren özel bir renk sistemi modeller, bir tür sistemi ekler, yeni bir yükseklik sistemi sunar ve MaterialTheme tarafından sağlanan diğer sistemleri hariç tutarız:

@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
}

Materyal bileşenlerini kullanma

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

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

Özel temanızda belirlediğiniz değerlere erişmenizi öneririz. Alternatif olarak, temanızda Color, TextStyle, Shape veya başka sistemler sağlanmıyorsa bunları sabit bir şekilde 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 = ContentAlpha.disabled)
        ),
        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)

Renk geçişlerini temsil etmek için List<Color> gibi yeni sınıf türleri eklediyseniz bileşenleri sarmalamak yerine sıfırdan uygulamanız daha iyi olabilir. Örneğin, Jetsnack örneğindeki JetsnackButton kısmına bakalım.