Jetpack Compose cung cấp cách triển khai Material Design, một hệ thống thiết kế toàn diện để tạo các giao diện kỹ thuật số. Các thành phần Material Design (nút, thẻ, nút chuyển, v.v.) được xây dựng dựa trên Tuỳ chỉnh giao diện Material để phản ánh tốt hơn thương hiệu của sản phẩm một cách có hệ thống. Giao diện Material chứa các thuộc tính màu sắc, kiểu chữ và hình dạng. Khi bạn tuỳ chỉnh các thuộc tính này, thay đổi sẽ tự động phản ánh trong các thành phần mà bạn sử dụng để xây dựng ứng dụng.
Jetpack Compose triển khai các khái niệm này bằng thành phần kết hợp (composable) MaterialTheme:
MaterialTheme( colors = // ... typography = // ... shapes = // ... ) { // app content }
Định cấu hình các tham số mà bạn truyền đến MaterialTheme để tuỳ chỉnh giao diện cho ứng dụng.
 
  Màu
Màu sắc được mô hình hoá trong Compose bằng lớp Color, một lớp chứa dữ liệu.
val Red = Color(0xffff0000) val Blue = Color(red = 0f, green = 0f, blue = 1f)
Mặc dù bạn có thể sắp xếp những lớp này theo bất cứ cách nào bạn muốn (như hằng số cấp cao nhất, trong một singleton hoặc trong cùng dòng đã được xác định), chúng tôi đặc biệt khuyên bạn nên chỉ định màu trong giao diện và truy xuất màu từ đó. Cách thức này giúp bạn có thể hỗ trợ giao diện tối và giao diện lồng nhau.
 
  Compose cung cấp lớp Colors để lập mô hình Hệ màu Material. Colors cung cấp các hàm trình tạo để tạo các nhóm màu sáng hoặc tối:
private val Yellow200 = Color(0xffffeb46) private val Blue200 = Color(0xff91a4fc) // ... private val DarkColors = darkColors( primary = Yellow200, secondary = Blue200, // ... ) private val LightColors = lightColors( primary = Yellow500, primaryVariant = Yellow400, secondary = Blue700, // ... )
Sau khi xác định Colors, bạn có thể truyền các màu đó đến MaterialTheme:
MaterialTheme( colors = if (darkTheme) DarkColors else LightColors ) { // app content }
Sử dụng màu giao diện
Bạn có thể truy xuất Colors được cung cấp cho thành phần kết hợp MaterialThemebằng cách sử dụng MaterialTheme.colors.
Text( text = "Hello theming", color = MaterialTheme.colors.primary )
Màu bề mặt và màu nội dung
Nhiều thành phần chấp nhận một cặp màu và màu nội dung:
Surface( color = MaterialTheme.colors.surface, contentColor = contentColorFor(color), // ... ) { /* ... */ } TopAppBar( backgroundColor = MaterialTheme.colors.primarySurface, contentColor = contentColorFor(backgroundColor), // ... ) { /* ... */ }
Do vậy, bạn có thể không chỉ thiết lập màu cho một thành phần kết hợp, mà còn cung cấp màu mặc định cho nội dung, các thành phần kết hợp trong đó. Nhiều thành phần kết hợp sử dụng màu nội dung này theo mặc định. Ví dụ: màu của Text dựa trên màu nội dung của lớp mẹ, còn Icon sử dụng màu đó để đặt sắc thái màu.
 
  Phương thức contentColorFor() truy xuất màu "trên" thích hợp cho màu của bất kỳ giao diện nào. Ví dụ: nếu bạn đặt màu nền primary trên Surface, thì hàm này sẽ dùng để đặt onPrimary làm màu nội dung.
Nếu đặt màu nền không theo giao diện, bạn cũng nên chỉ định màu nội dung phù hợp. Sử dụng LocalContentColor để truy xuất màu nội dung ưu tiên cho nền hiện tại, tại một vị trí nhất định trong hệ phân cấp.
Độ đậm nhạt của nội dung
Thông thường, bạn muốn thay đổi mức độ nhấn mạnh nội dung để truyền tải mức độ quan trọng và mang lại sự phân cấp thị giác. Các tài liệu đề xuất về mức độ dễ đọc văn bản trong Material Design khuyến cáo nên sử dụng nhiều mức độ đậm nhạt để truyền tải các mức độ quan trọng khác nhau.
Jetpack Compose triển khai việc này bằng cách sử dụng LocalContentAlpha. Bạn có thể chỉ định độ đậm nhạt (alpha) của nội dung cho hệ thống phân cấp bằng cách cung cấp giá trị cho CompositionLocal. Các thành phần kết hợp lồng ghép có thể sử dụng giá trị này để áp dụng biện pháp xử lý độ đậm nhạt cho nội dung của chúng. Ví dụ: theo mặc định, Text và Icon sử dụng tổ hợp LocalContentColor được điều chỉnh để sử dụng LocalContentAlpha. Material chỉ định một số giá trị alpha chuẩn (high, medium, disabled) được mô hình hoá bằng đối tượng ContentAlpha.
// By default, both Icon & Text use the combination of LocalContentColor & // LocalContentAlpha. De-emphasize content by setting content alpha CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { Text( // ... ) } CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) { Icon( // ... ) Text( // ... ) }
Để tìm hiểu thêm về CompositionLocal, hãy xem phần Dữ liệu trong phạm vi cục bộ với CompositionLocal.
 
  ContentAlpha.high. Dòng thứ hai chứa siêu dữ liệu ít quan trọng hơn, do đó sử dụng ContentAlpha.medium.Giao diện tối
Trong Compose, bạn cài đặt giao diện tối và sáng bằng cách cung cấp các nhóm Colors khác nhau cho thành phần kết hợp MaterialTheme:
@Composable fun MyTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { MaterialTheme( colors = if (darkTheme) DarkColors else LightColors, /*...*/ content = content ) }
Trong ví dụ này, MaterialTheme được bọc trong một thành phần kết hợp riêng chấp nhận một tham số chỉ định xem có nên sử dụng giao diện tối hay không. Trong trường hợp này, hàm sẽ nhận giá trị mặc định cho darkTheme bằng cách truy vấn
chế độ cài đặt giao diện trên thiết bị.
Bạn có thể sử dụng mã như thế này để kiểm tra xem Colors hiện tại là sáng hay tối:
val isLightTheme = MaterialTheme.colors.isLight Icon( painterResource( id = if (isLightTheme) { R.drawable.ic_sun_24 } else { R.drawable.ic_moon_24 } ), contentDescription = "Theme" )
Lớp phủ độ nâng
Trong Material, các bề mặt trong giao diện tối có độ nâng cao hơn sẽ nhận được lớp phủ độ nâng để làm sáng nền tương ứng. Bề mặt có độ nâng càng cao (nâng độ nâng lên gần với nguồn sáng ngầm ẩn) thì bề mặt đó càng sáng.
Thành phần kết hợp Surface sẽ tự động áp dụng các lớp phủ này khi sử dụng màu tối, cũng như mọi thành phần kết hợp Material khác sử dụng một bề mặt:
Surface( elevation = 2.dp, color = MaterialTheme.colors.surface, // color will be adjusted for elevation /*...*/ ) { /*...*/ }
 
  surface làm màu nền. Vì thẻ và phần điều hướng dưới cùng có độ nâng khác nhau phía trên nền, nên màu sắc của chúng sẽ có sự khác biệt nhẹ – màu thẻ sáng hơn màu nền và màu của phần điều hướng dưới cùng sáng hơn màu thẻ.Đối với các trường hợp tuỳ chỉnh không liên quan đến Surface, hãy sử dụng LocalElevationOverlay, một CompositionLocal chứa ElevationOverlay được dùng trong các thành phần Surface:
// Elevation overlays // Implemented in Surface (and any components that use it) val color = MaterialTheme.colors.surface val elevation = 4.dp val overlaidColor = LocalElevationOverlay.current?.apply( color, elevation )
Để tắt lớp phủ nâng, hãy cung cấp null tại điểm đã chọn trong hệ phân cấp thành phần kết hợp:
MyTheme { CompositionLocalProvider(LocalElevationOverlay provides null) { // Content without elevation overlays } }
Hạn chế các màu nhấn
Material Design đề xuất áp dụng một số màu nhấn hạn chế cho giao diện tối bằng cách ưu tiên sử dụng màu surface thay cho màu primary trong hầu hết các trường hợp. Theo mặc định, các thành phần kết hợp trong Material như TopAppBar và BottomNavigation
sẽ triển khai hành vi này.
 
  Đối với các trường hợp tuỳ chỉnh, hãy sử dụng thuộc tính mở rộng primarySurface:
Surface( // Switches between primary in light theme and surface in dark theme color = MaterialTheme.colors.primarySurface, /*...*/ ) { /*...*/ }
Kiểu chữ
Material xác định hệ thống kiểu chữ, khuyến khích bạn sử dụng một số ít các kiểu được đặt tên theo ngữ nghĩa.
 
  Compose triển khai hệ thống kiểu chữ bằng lớp Typography, TextStyle và các lớp liên quan đến phông chữ. Hàm khởi tạo Typography cung cấp các tuỳ chọn mặc định cho từng kiểu để bạn có thể bỏ qua bất kỳ kiểu nào bạn không muốn tuỳ chỉnh:
val raleway = FontFamily( Font(R.font.raleway_regular), Font(R.font.raleway_medium, FontWeight.W500), Font(R.font.raleway_semibold, FontWeight.SemiBold) ) val myTypography = Typography( h1 = TextStyle( fontFamily = raleway, fontWeight = FontWeight.W300, fontSize = 96.sp ), body1 = TextStyle( fontFamily = raleway, fontWeight = FontWeight.W600, fontSize = 16.sp ) /*...*/ ) MaterialTheme(typography = myTypography, /*...*/) { /*...*/ }
Nếu bạn muốn sử dụng cùng một kiểu chữ, hãy chỉ định tham số defaultFontFamily và bỏ qua fontFamily của bất kỳ phần tử TextStyle nào:
val typography = Typography(defaultFontFamily = raleway) MaterialTheme(typography = typography, /*...*/) { /*...*/ }
Sử dụng kiểu văn bản
Các phần tử TextStyle được truy cập bằng MaterialTheme.typography. Truy xuất các phần tử TextStyle như sau:
Text( text = "Subtitle2 styled", style = MaterialTheme.typography.subtitle2 )
 
  Hình dạng
Material xác định hệ thống hình dạng, cho phép bạn xác định các hình dạng cho các thành phần lớn, vừa và nhỏ.
 
  Compose triển khai hệ thống hình dạng bằng lớp Shapes, cho phép bạn chỉ định CornerBasedShape cho từng danh mục kích thước:
val shapes = Shapes( small = RoundedCornerShape(percent = 50), medium = RoundedCornerShape(0f), large = CutCornerShape( topStart = 16.dp, topEnd = 0.dp, bottomEnd = 0.dp, bottomStart = 16.dp ) ) MaterialTheme(shapes = shapes, /*...*/) { /*...*/ }
Nhiều thành phần sử dụng các hình dạng này theo mặc định. Ví dụ: Button, TextField và FloatingActionButton mặc định là nhỏ, AlertDialog mặc định là trung bình và ModalDrawer mặc định là lớn – hãy xem tham chiếu lược đồ hình dạng để biết thông tin liên kết đầy đủ.
Sử dụng hình dạng
Các phần tử Shape được truy cập bằng MaterialTheme.shapes. Truy xuất các phần tử Shape bằng mã như sau:
Surface( shape = MaterialTheme.shapes.medium, /*...*/ ) { /*...*/ }
 
  Kiểu mặc định
Không có khái niệm tương đương về các kiểu mặc định trong Compose từ Chế độ xem Android. Bạn có thể cung cấp chức năng tương tự bằng cách tạo các hàm có khả năng kết hợp overload riêng để bao bọc các thành phần Material. Ví dụ: để tạo kiểu nút, hãy gói nút trong hàm có khả năng kết hợp của riêng bạn, trực tiếp đặt các tham số bạn muốn hoặc cần thay đổi và cấp quyền truy cập cho các tham số khác dưới dạng tham số cho thành phần kết hợp chứa tham số.
@Composable fun MyButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colors.secondary ), onClick = onClick, modifier = modifier, content = content ) }
Lớp phủ giao diện
Bạn có thể đạt được mức tương đương với lớp phủ giao diện trong thành phần hiển thị Android trong Compose bằng cách lồng ghép thành phần kết hợp MaterialTheme. Vì MaterialTheme đặt giá trị mặc định cho màu sắc, kiểu chữ và hình dạng theo giá trị giao diện hiện tại, nên tất cả các tham số khác sẽ giữ nguyên giá trị mặc định khi một giao diện chỉ đặt một trong các tham số đó.
Ngoài ra, khi di chuyển màn hình dựa trên Khung hiển thị sang Compose, hãy lưu ý đến cách sử dụng thuộc tính android:theme. Có thể bạn cần một MaterialTheme mới trong phần đó của cây giao diện người dùng Compose.
Trong ví dụ này, màn hình chi tiết sử dụng PinkTheme cho phần lớn màn hình, sau đó dùng BlueTheme cho phần có liên quan. Ảnh chụp màn hình và mã sau đây minh hoạ khái niệm này:
 
  
@Composable fun DetailsScreen(/* ... */) { PinkTheme { // other content RelatedSection() } } @Composable fun RelatedSection(/* ... */) { BlueTheme { // content } }
Trạng thái thành phần
Các thành phần Material có thể tương tác (được nhấp, bật/tắt, v.v.) có thể ở các trạng thái thị giác khác nhau. Các trạng thái bao gồm bật, tắt, được nhấn, v.v.
Các thành phần kết hợp thường có tham số enabled. Việc đặt thành false sẽ ngăn tương tác và thay đổi các thuộc tính như màu sắc và elevation để cho biết trạng thái thành phần một cách trực quan.
 
  enabled = true (bên trái) và enabled = false (bên phải).Trong hầu hết các trường hợp, bạn có thể dựa vào giá trị mặc định như màu sắc và elevation. Nếu cần định cấu hình các giá trị dùng trong các trạng thái khác nhau, bạn có thể dùng các lớp và hàm tiện lợi có sẵn. Hãy xem ví dụ sau về nút:
Button( onClick = { /* ... */ }, enabled = true, // Custom colors for different states colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colors.secondary, disabledBackgroundColor = MaterialTheme.colors.onBackground .copy(alpha = 0.2f) .compositeOver(MaterialTheme.colors.background) // Also contentColor and disabledContentColor ), // Custom elevation for different states elevation = ButtonDefaults.elevation( defaultElevation = 8.dp, disabledElevation = 2.dp, // Also pressedElevation ) ) { /* ... */ }
 
 enabled = true (trái) và enabled = false (phải), với các giá trị màu và độ nâng đã điều chỉnh.Hiệu ứng gợn sóng
Các thành phần trong Material Design sử dụng hiệu ứng gợn sóng để cho biết chúng đang được tương tác. Nếu bạn đang sử dụng MaterialTheme trong hệ thống phân cấp, thì Ripple sẽ được dùng làm Indication mặc định bên trong các đối tượng sửa đổi, chẳng hạn như clickable và indication.
Trong hầu hết các trường hợp, bạn có thể sử dụng Ripple mặc định. Nếu cần định cấu hình giao diện của các thành phần này, bạn có thể dùng RippleTheme để thay đổi các thuộc tính như màu sắc và alpha.
Bạn có thể mở rộng RippleTheme và sử dụng các hàm số hiệu dụng defaultRippleColor và defaultRippleAlpha. Sau đó, bạn có thể cung cấp giao diện gợn sóng tuỳ chỉnh trong hệ thống phân cấp bằng cách sử dụng LocalRippleTheme:
@Composable fun MyApp() { MaterialTheme { CompositionLocalProvider( LocalRippleTheme provides SecondaryRippleTheme ) { // App content } } } @Immutable private object SecondaryRippleTheme : RippleTheme { @Composable override fun defaultColor() = RippleTheme.defaultRippleColor( contentColor = MaterialTheme.colors.secondary, lightTheme = MaterialTheme.colors.isLight ) @Composable override fun rippleAlpha() = RippleTheme.defaultRippleAlpha( contentColor = MaterialTheme.colors.secondary, lightTheme = MaterialTheme.colors.isLight ) }
 
  RippleTheme.Tìm hiểu thêm
Để tìm hiểu thêm về Tuỳ chỉnh Material Design trong Compose, hãy tham khảo các tài nguyên khác sau đây.
Lớp học lập trình
Video
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Hệ thống thiết kế tuỳ chỉnh trong Compose
- Di chuyển từ Material 2 sang Material 3 trong Compose
- Hỗ trợ tiếp cận trong Compose
