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

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

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

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

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

گسترش تم مواد

Compose Material از نزدیک Material Theming را مدل‌سازی می‌کند تا پیروی از دستورالعمل‌های Material را ساده و ایمن کند. با این حال، می توان مجموعه رنگ، تایپوگرافی و شکل را با مقادیر اضافی گسترش داد.

ساده ترین روش افزودن ویژگی های افزونه است:

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

این یک سازگاری با API های استفاده از MaterialTheme را فراهم می کند. نمونه‌ای از این تعریف شده توسط Compose، primarySurface است که بسته به Colors.isLight به عنوان یک پروکسی بین primary و surface عمل می‌کند.

رویکرد دیگر تعریف یک تم گسترده است که MaterialTheme و مقادیر آن را "پیچیده" می کند.

فرض کنید می خواهید دو رنگ اضافی - tertiary و onTertiary - اضافه کنید و در عین حال رنگ های مواد موجود را حفظ کنید:

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

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

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

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

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

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

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

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

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

پیاده سازی سیستم طراحی کاملا سفارشی

ممکن است بخواهید موضوع Material Theming را با یک سیستم طراحی کاملاً سفارشی جایگزین کنید. در نظر بگیرید که MaterialTheme سیستم های زیر را ارائه می دهد:

  • Colors ، Typography ، و Shapes : سیستم‌های تم‌بندی مواد
  • ContentAlpha : سطوح شفافیت برای انتقال تاکید در Text و Icon
  • TextSelectionColors : رنگ‌هایی که برای انتخاب متن توسط Text و TextField استفاده می‌شوند
  • Ripple و RippleTheme : اجرای مواد Indication

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

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

توصیه می کنیم به مقادیری که از طرح زمینه سفارشی خود تنظیم کرده اید دسترسی داشته باشید. از طرف دیگر، اگر تم شما 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 = 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)

اگر انواع کلاس های جدیدی را معرفی کرده اید - مانند List<Color> برای نشان دادن گرادیان ها - ممکن است بهتر باشد به جای بسته بندی کامپوننت ها از ابتدا آنها را پیاده سازی کنید. برای مثال، نگاهی به JetsnackButton از نمونه Jetsnack بیندازید.

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}