Compose में कस्टम डिज़ाइन सिस्टम जोड़ना

हमारा सुझाव है कि आप Material डिज़ाइन सिस्टम का इस्तेमाल करें. Jetpack Compose में Material का इंप्लीमेंटेशन शामिल है. हालांकि, आपको इसका इस्तेमाल करने के लिए मजबूर नहीं किया जाता. Material को पूरी तरह से सार्वजनिक एपीआई पर बनाया गया है. इसलिए, इसी तरह से अपना डिज़ाइन सिस्टम बनाया जा सकता है.

इसके लिए, कई तरीके अपनाए जा सकते हैं:

ऐसा भी हो सकता है कि आपको कस्टम डिज़ाइन सिस्टम के साथ Material कॉम्पोनेंट का इस्तेमाल जारी रखना हो. ऐसा किया जा सकता है. हालांकि, आपको कुछ बातों का ध्यान रखना होगा, ताकि आपके चुने गए तरीके के हिसाब से काम हो सके.

MaterialTheme और कस्टम डिज़ाइन सिस्टम में इस्तेमाल होने वाले लोअर-लेवल कंस्ट्रक्ट और एपीआई के बारे में ज़्यादा जानने के लिए, Compose में थीम की संरचना गाइड देखें.

मटीरियल थीम को बढ़ाना

Compose Material, Material Theming के हिसाब से काम करता है, ताकि Material के दिशा-निर्देशों का पालन करना आसान और टाइप-सेफ़ हो. हालांकि, रंग, टाइपोग्राफ़ी, और आकार के सेट को अतिरिक्त वैल्यू के साथ बढ़ाया जा सकता है.

एक्सटेंशन प्रॉपर्टी जोड़ने का सबसे आसान तरीका यह है:

// 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)

इससे MaterialTheme के इस्तेमाल से जुड़े एपीआई के साथ एक जैसा अनुभव मिलता है. Compose में पहले से तय किए गए इस तरह के उदाहरण में surfaceColorAtElevation शामिल है. यह एलिवेशन के आधार पर, इस्तेमाल किए जाने वाले सर्फ़ेस के रंग का पता लगाता है.

एक और तरीका यह है कि एक एक्सटेंडेड थीम तय की जाए, जो MaterialTheme और उसकी वैल्यू को "रैप" करती है.

मान लें कि आपको दो और रंग जोड़ने हैं — caution और onCaution. caution रंग का इस्तेमाल उन कार्रवाइयों के लिए किया जाता है जो कुछ हद तक खतरनाक होती हैं. साथ ही, आपको मौजूदा Material रंग भी रखने हैं:

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

यह MaterialTheme के इस्तेमाल से जुड़े एपीआई की तरह ही है. यह कई थीम के साथ भी काम करता है, क्योंकि ExtendedTheme को MaterialTheme की तरह ही नेस्ट किया जा सकता है.

मटीरियल कॉम्पोनेंट का इस्तेमाल करना

मटेरियल थीमिंग को बढ़ाने पर, मौजूदा MaterialTheme वैल्यू बनी रहती हैं और मटेरियल कॉम्पोनेंट में अब भी डिफ़ॉल्ट वैल्यू मौजूद होती हैं.

अगर आपको कॉम्पोनेंट में एक्सटेंड की गई वैल्यू का इस्तेमाल करना है, तो उन्हें अपने कंपोज़ेबल फ़ंक्शन में रैप करें. इसके बाद, उन वैल्यू को सीधे तौर पर सेट करें जिनमें आपको बदलाव करना है. साथ ही, अन्य वैल्यू को कंपोज़ेबल फ़ंक्शन में पैरामीटर के तौर पर दिखाएं:

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

इसके बाद, आपको Button की जगह ExtendedButton का इस्तेमाल करना होगा.

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

मटेरियल सब-सिस्टम को बदलना

ऐसा हो सकता है कि आपको Material Theming को बढ़ाने के बजाय, एक या उससे ज़्यादा सिस्टम — Colors, Typography या Shapes — को कस्टम थीम के साथ बदलना हो. हालांकि, ऐसा करते समय आपको अन्य सिस्टम को बनाए रखना होगा.

मान लें कि आपको रंग सिस्टम को बनाए रखते हुए, टाइप और शेप सिस्टम को बदलना है:

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

मटीरियल कॉम्पोनेंट का इस्तेमाल करना

जब MaterialTheme के एक या उससे ज़्यादा सिस्टम बदल दिए जाते हैं, तो Material components का इस्तेमाल करने से, Material के रंग, टाइप या आकार की अवांछित वैल्यू मिल सकती हैं.

अगर आपको कॉम्पोनेंट में बदली गई वैल्यू का इस्तेमाल करना है, तो उन्हें अपने कंपोज़ेबल फ़ंक्शन में रैप करें. साथ ही, सीधे तौर पर संबंधित सिस्टम के लिए वैल्यू सेट करें. इसके अलावा, अन्य वैल्यू को कंपोज़ेबल फ़ंक्शन में शामिल करने के लिए, उन्हें पैरामीटर के तौर पर दिखाएं.

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

इसके बाद, आपको Button की जगह ReplacementButton का इस्तेमाल करना होगा.

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

पूरी तरह से कस्टम डिज़ाइन सिस्टम लागू करना

ऐसा हो सकता है कि आपको मटीरियल थीमिंग को पूरी तरह से कस्टम डिज़ाइन सिस्टम से बदलना हो. मान लें कि MaterialTheme ये सिस्टम उपलब्ध कराता है:

  • Colors, Typography, और Shapes: मटीरियल थीमिंग सिस्टम
  • TextSelectionColors: Text और TextField की मदद से टेक्स्ट चुनने के लिए इस्तेमाल किए गए रंग
  • Ripple और RippleTheme: Indication का मटीरियल इंप्लिमेंटेशन

अगर आपको Material कॉम्पोनेंट का इस्तेमाल जारी रखना है, तो आपको अपनी कस्टम थीम या थीम में इन सिस्टम में से कुछ को बदलना होगा. इसके अलावा, आपको अपने कॉम्पोनेंट में सिस्टम को मैनेज करना होगा, ताकि अनचाही गतिविधि से बचा जा सके.

हालांकि, डिज़ाइन सिस्टम सिर्फ़ उन कॉन्सेप्ट पर आधारित नहीं होते जिन पर Material निर्भर करता है. मौजूदा सिस्टम में बदलाव किया जा सकता है. साथ ही, नई क्लास और टाइप के साथ पूरी तरह से नए सिस्टम जोड़े जा सकते हैं. इससे अन्य कॉन्सेप्ट को थीम के साथ काम करने लायक बनाया जा सकता है.

नीचे दिए गए कोड में, हमने कस्टम कलर सिस्टम को मॉडल किया है. इसमें ग्रेडिएंट (List<Color>) शामिल हैं. साथ ही, इसमें टाइप सिस्टम और नया एलिवेशन सिस्टम शामिल किया गया है. इसके अलावा, MaterialTheme की ओर से उपलब्ध कराए गए अन्य सिस्टम को शामिल नहीं किया गया है:

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

मटीरियल कॉम्पोनेंट का इस्तेमाल करना

MaterialTheme मौजूद न होने पर, Material कॉम्पोनेंट का इस्तेमाल करने से, Material के रंग, टाइप, और आकार की अनचाही वैल्यू मिलेंगी. साथ ही, इंडिकेशन के तौर-तरीके में भी बदलाव होगा.

अगर आपको कॉम्पोनेंट में कस्टम वैल्यू का इस्तेमाल करना है, तो उन्हें अपने कंपोज़ेबल फ़ंक्शन में रैप करें. साथ ही, सीधे तौर पर सिस्टम के लिए काम की वैल्यू सेट करें. इसके अलावा, अन्य वैल्यू को कंपोज़ेबल कॉम्पोनेंट के पैरामीटर के तौर पर दिखाएं.

हमारा सुझाव है कि आपने कस्टम थीम में जो वैल्यू सेट की हैं उन्हें ऐक्सेस करें. इसके अलावा, अगर आपकी थीम में Color, TextStyle, Shape या अन्य सिस्टम उपलब्ध नहीं हैं, तो उन्हें हार्डकोड किया जा सकता है.

@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>, तो रैप करने के बजाय कॉम्पोनेंट को शुरू से लागू करना बेहतर हो सकता है. उदाहरण के लिए, Jetsnack के सैंपल में मौजूद JetsnackButton देखें.