Tuy Material là hệ thống thiết kế mà chúng tôi đề xuất và Jetpack Compose hỗ trợ việc triển khai Material, nhưng bạn không nhất thiết phải sử dụng Material. Material được xây dựng hoàn toàn trên các API công khai, vì vậy, bạn cũng có thể tạo hệ thống thiết kế của riêng mình theo cách tương tự.
Sau đây là một số phương pháp bạn có thể thực hiện:
- Mở rộng
MaterialTheme
bằng cách thêm giá trị giao diện bổ sung - Thay thế một hoặc nhiều hệ thống Material –
Colors
,Typography
hoặcShapes
— bằng cách triển khai các tuỳ chọn triển khai tuỳ chỉnh, trong khi vẫn duy trì các hệ thống khác - Triển khai hệ thống thiết kế tuỳ chỉnh hoàn toàn để thay thế
MaterialTheme
Bạn cũng nên tiếp tục sử dụng thành phần Material với một hệ thống thiết kế tuỳ chỉnh. Bạn có thể thực hiện việc này nhưng có một số điểm cần lưu ý để phù hợp với phương pháp mà bạn thực hiện.
Để tìm hiểu thêm về API và cấu trúc cấp thấp hơn mà MaterialTheme
và các hệ thống thiết kế tuỳ chỉnh sử dụng, hãy tham khảo hướng dẫn Cấu tạo của một giao diện trong Compose.
Mở rộng giao diện Material
Compose Material lập mô hình Sắp xếp theo chủ đề Material một cách chặt chẽ để đảm bảo tính năng này đơn giản và an toàn về loại nhằm tuân thủ nguyên tắc của Material Design. Tuy nhiên, bạn có thể mở rộng các bộ màu, kiểu chữ và hình dạng bằng các giá trị bổ sung.
Cách đơn giản nhất là thêm thuộc tính mở rộng:
// 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)
Việc này giúp đảm bảo tính nhất quán giữa các API sử dụng MaterialTheme
. Có thể kể đến một ví dụ do chính Compose xác định, đó là surfaceColorAtElevation
hoạt động như một proxy giữa và , tuỳ thuộc vào độ nâng.
Một phương pháp khác là xác định một giao diện mở rộng "gói" MaterialTheme
và các giá trị của giao diện đó.
Giả sử bạn muốn thêm 2 màu bổ sung — caution
và onCaution
, một màu vàng dùng cho các hành động có độ nguy hiểm trung bình — trong khi vẫn giữ các màu Material hiện có:
@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 }
Điều này tương tự như các API sử dụng MaterialTheme
. Cách này cũng hỗ trợ nhiều giao diện do bạn có thể lồng các ExtendedTheme
giống như MaterialTheme
.
Sử dụng thành phần Material
Khi mở rộng phạm vi Sắp xếp theo chủ đề Material, các giá trị MaterialTheme
hiện có sẽ được duy trì và các thành phần Material vẫn có giá trị mặc định hợp lý.
Nếu bạn muốn sử dụng giá trị được mở rộng trong các thành phần, hãy gói các giá trị đó vào hàm có khả năng kết hợp của riêng bạn, trực tiếp đặt các giá trị mà bạn muốn thay đổi và cấp quyền truy cập cho các giá trị khác dưới dạng tham số cho thành phần kết hợp (composable) chứa tham số:
@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 ) }
Sau đó, bạn cần thay thế việc sử dụng Button
bằng ExtendedButton
khi thích hợp.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Thay thế các hệ thống phụ của Material
Thay vì mở rộng phạm vi Sắp xếp theo chủ đề Material, bạn cũng có thể thay thế một hoặc nhiều hệ thống — Colors
, Typography
hay Shapes
— bằng một phương thức triển khai tuỳ chỉnh, trong khi vẫn duy trì các hệ thống khác.
Giả sử bạn muốn thay thế các hệ thống kiểu (type) và hình dạng (shape) trong khi vẫn giữ hệ thống màu (color):
@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 }
Sử dụng thành phần Material
Khi một hoặc nhiều hệ thống của MaterialTheme
được thay thế, việc sử dụng các thành phần Material nguyên trạng có thể dẫn đến các giá trị, kiểu hoặc hình dạng Material không mong muốn.
Nếu bạn muốn sử dụng giá trị thay thế trong các thành phần, hãy gói các giá trị đó vào hàm có khả năng kết hợp của riêng bạn, trực tiếp đặt các giá trị phù hợp với hệ thống và cấp quyền truy cập cho các giá trị khác dưới dạng tham số cho thành phần kết hợp chứa tham số.
@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() } } ) }
Sau đó, bạn cần thay thế việc sử dụng Button
bằng ReplacementButton
khi thích hợp.
@Composable fun ReplacementApp() { ReplacementTheme { /*...*/ ReplacementButton(onClick = { /* ... */ }) { /* ... */ } } }
Triển khai hệ thống thiết kế tuỳ chỉnh hoàn toàn
Bạn cũng có thể thay thế chủ đề Material bằng một hệ thống thiết kế tuỳ chỉnh hoàn toàn.
Hãy cân nhắc việc MaterialTheme
cung cấp các hệ thống sau đây:
Colors
,Typography
vàShapes
: Hệ thống giao diện MaterialTextSelectionColors
: Màu sắc đượcText
vàTextField
sử dụng để chọn văn bảnRipple
vàRippleTheme
: Cách triển khai của Material choIndication
Nếu muốn tiếp tục sử dụng các thành phần Material, bạn cần thay thế một số hệ thống này trong giao diện hoặc giao diện tuỳ chỉnh của bạn hoặc xử lý các hệ thống trong thành phần của bạn để tránh hành vi không mong muốn.
Tuy nhiên, hệ thống thiết kế không giới hạn ở các khái niệm mà Material dựa trên đó. Bạn có thể sửa đổi các hệ thống hiện có và đưa ra các hệ thống hoàn toàn mới – với các lớp (class) và kiểu (type) mới – để các khái niệm khác tương thích với giao diện của mình.
Trong mã sau, chúng tôi mô hình hoá một hệ thống màu tuỳ chỉnh bao gồm các màu chuyển màu (List<Color>
) (bao gồm cả một hệ thống kiểu) cho thấy một hệ thống nâng (elevation) mới và loại trừ các hệ thống khác do MaterialTheme
cung cấp:
@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 }
Sử dụng thành phần Material
Khi không có MaterialTheme
, việc sử dụng các thành phần Material nguyên trạng sẽ dẫn đến các giá trị màu, kiểu và hình dạng Material không mong muốn cũng như hành vi chỉ báo không mong muốn.
Nếu bạn muốn sử dụng giá trị tuỳ chỉnh trong các thành phần, hãy gói các giá trị đó vào hàm có khả năng kết hợp của riêng bạn, trực tiếp đặt các giá trị phù hợp với hệ thống và cấp quyền truy cập cho các giá trị khác dưới dạng tham số cho thành phần kết hợp chứa tham số.
Bạn nên truy cập vào các giá trị mà bạn đặt qua giao diện tuỳ chỉnh của mình.
Ngoài ra, nếu giao diện của bạn không cung cấp Color
, TextStyle
, Shape
hoặc các hệ thống khác, thì bạn có thể mã hoá cứng các giao diện đó.
@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)
Nếu đã ra mắt các loại lớp mới – chẳng hạn như List<Color>
để đại diện cho hiệu ứng chuyển màu (gradient) – thì bạn nên triển khai các thành phần từ đầu thay vì gói các thành phần đó. Ví dụ: hãy xem JetsnackButton
trong mẫu Jetsnack.
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Material Design 3 trong Compose
- Di chuyển từ Material 2 sang Material 3 trong Compose
- Phân tích một giao diện trong Compose