سیستم های طراحی سفارشی در Compose

اگرچه Material سیستم طراحی پیشنهادی ماست و Jetpack Compose پیاده‌سازی‌ای از Material را ارائه می‌دهد، اما شما مجبور به استفاده از آن نیستید. Material کاملاً بر اساس APIهای عمومی ساخته شده است، بنابراین می‌توانید سیستم طراحی خود را به همان روش ایجاد کنید.

چندین رویکرد وجود دارد که می‌توانید اتخاذ کنید:

همچنین ممکن است بخواهید به استفاده از کامپوننت‌های متریال با یک سیستم طراحی سفارشی ادامه دهید. انجام این کار امکان‌پذیر است، اما نکاتی وجود دارد که باید در نظر داشته باشید تا با رویکردی که در پیش گرفته‌اید، مطابقت داشته باشد.

برای کسب اطلاعات بیشتر در مورد سازه‌های سطح پایین و APIهای مورد استفاده توسط MaterialTheme و سیستم‌های طراحی سفارشی، به بخش آناتومی یک قالب در راهنمای Compose مراجعه کنید.

گسترش تم متریال

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

این امر سازگاری با APIهای استفاده از MaterialTheme را فراهم می‌کند. نمونه‌ای از این مورد که توسط خود Compose تعریف شده است، surfaceColorAtElevation است که رنگ سطحی را که باید بسته به ارتفاع استفاده شود، تعیین می‌کند.

رویکرد دیگر، تعریف یک تم توسعه‌یافته است که MaterialTheme و مقادیر آن را در بر می‌گیرد.

فرض کنید می‌خواهید دو رنگ اضافی اضافه کنید - caution و 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
}

این شبیه به APIهای استفاده از MaterialTheme است. همچنین از چندین تم پشتیبانی می‌کند زیرا می‌توانید ExtendedTheme ها را مانند MaterialTheme به صورت تو در تو قرار دهید.

از اجزای متریال استفاده کنید

هنگام گسترش قالب‌بندی متریال، مقادیر موجود MaterialTheme حفظ می‌شوند و کامپوننت‌های متریال همچنان مقادیر پیش‌فرض معقولی دارند.

اگر می‌خواهید از مقادیر توسعه‌یافته در کامپوننت‌ها استفاده کنید، آن‌ها را در توابع composable خودتان قرار دهید، مقادیری را که می‌خواهید تغییر دهید مستقیماً تنظیم کنید و مقادیر دیگر را به عنوان پارامتر در اختیار composable حاوی آن‌ها قرار دهید:

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

زیرسیستم‌های جایگزینی مواد

به جای گسترش قالب‌بندی متریال، ممکن است بخواهید یک یا چند سیستم - 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 به همان صورت ممکن است منجر به مقادیر رنگ، نوع یا شکل ناخواسته در Material شود.

اگر می‌خواهید از مقادیر جایگزین در کامپوننت‌ها استفاده کنید، آن‌ها را در توابع composable خودتان قرار دهید، مقادیر را مستقیماً برای سیستم مربوطه تنظیم کنید و مقادیر دیگر را به عنوان پارامتر در اختیار composable حاوی آن‌ها قرار دهید.

@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

اگر می‌خواهید به استفاده از کامپوننت‌های متریال ادامه دهید، باید برخی از این سیستم‌ها را در قالب یا قالب‌های سفارشی خود جایگزین کنید، یا سیستم‌های موجود در کامپوننت‌های خود را مدیریت کنید تا از رفتارهای ناخواسته جلوگیری شود.

با این حال، سیستم‌های طراحی محدود به مفاهیمی نیستند که متریال به آنها متکی است. شما می‌توانید سیستم‌های موجود را تغییر داده و سیستم‌های کاملاً جدیدی - با کلاس‌ها و انواع جدید - معرفی کنید تا مفاهیم دیگر با قالب‌ها سازگار شوند.

در کد زیر، ما یک سیستم رنگ سفارشی را مدل‌سازی می‌کنیم که شامل گرادیان‌ها ( 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 خواهد شد.

اگر می‌خواهید از مقادیر سفارشی در کامپوننت‌ها استفاده کنید، آن‌ها را در توابع composable خودتان قرار دهید، مقادیر را مستقیماً برای سیستم مربوطه تنظیم کنید و مقادیر دیگر را به عنوان پارامتر در اختیار composable حاوی آن‌ها قرار دهید.

توصیه می‌کنیم به مقادیری که از قالب سفارشی خود تنظیم می‌کنید دسترسی داشته باشید. از طرف دیگر، اگر قالب شما Color ، TextStyle ، Shape یا سیستم‌های دیگر را ارائه نمی‌دهد، می‌توانید آنها را به صورت hardcode تنظیم کنید.

@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> برای نمایش گرادیان‌ها - شاید بهتر باشد به جای بسته‌بندی کامپوننت‌ها، آنها را از ابتدا پیاده‌سازی کنید. برای مثال، به JetsnackButton از نمونه Jetsnack نگاهی بیندازید.

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}