1. Giới thiệu
Trong lớp học lập trình này, bạn sẽ tìm hiểu cách thiết kế giao diện cho ứng dụng trong Jetpack Compose bằng Material Design 3. Bạn cũng sẽ tìm hiểu về thành phần chính của Bảng phối màu, kiểu chữ và hình dạng trong Material Design 3, giúp bạn tạo chủ đề dễ tiếp cận và cá nhân hoá cho người dùng.
Ngoài ra, bạn còn tìm hiểu về khả năng hỗ trợ tuỳ chỉnh giao diện động cùng với các cấp độ nhấn mạnh khác nhau.
Kiến thức bạn sẽ học được
Trong lớp học lập trình này, bạn sẽ tìm hiểu:
- Các khía cạnh chính trong thiết kế giao diện bằng Material 3
- Bảng phối màu trong Material 3 và cách tạo giao diện cho ứng dụng
- Thêm giao diện động và giao diện sáng/tối cho ứng dụng
- Kiểu chữ và hình dạng để cá nhân hoá ứng dụng
- Thành phần trong Material 3 và cách tuỳ chỉnh các thành phần này để tạo kiểu giao diện cho ứng dụng
Sản phẩm bạn sẽ tạo ra
Trong lớp học lập trình này, bạn sẽ tạo giao diện cho một ứng dụng email khách có tên là Reply (Trả lời). Hãy bắt đầu bằng một ứng dụng chưa định kiểu, sử dụng giao diện cơ sở và sẽ áp dụng những gì bạn học được để tạo giao diện cho ứng dụng và hỗ trợ giao diện tối.
Màn khởi đầu mặc định của ứng dụng khi dùng giao diện cơ sở.
Bạn sẽ tạo giao diện với bảng phối màu, kiểu chữ và hình dạng rồi áp dụng những thành phần này cho danh sách email và trang chi tiết của ứng dụng. Bạn cũng sẽ thêm giao diện động cho ứng dụng. Khi kết thúc lớp học lập trình, ứng dụng của bạn sẽ có màu sắc và giao diện động.
Kết quả của lớp học lập trình tuỳ chỉnh giao diện với giao diện sáng và giao diện động sáng.
Kết quả của lớp học lập trình tuỳ chỉnh giao diện với giao diện tối và giao diện động tối.
Bạn cần có
- Phiên bản mới nhất của Android Studio
- Kinh nghiệm cơ bản về ngôn ngữ Kotlin
- Kiến thức cơ bản về Jetpack Compose
- Quen thuộc với bố cục trong Compose, chẳng hạn như Hàng, Cột và Đối tượng sửa đổi
2. Thiết lập
Ở bước này, bạn cần tải mã nguồn đầy đủ của ứng dụng Reply — ứng dụng bạn sẽ tạo kiểu trong lớp học lập trình này.
Lấy mã nguồn
Bạn có thể tìm thấy đoạn mã dành cho lớp học lập trình này trong codelab-android-compose trên kho lưu trữ GitHub. Để sao chép, hãy chạy:
$ git clone https://github.com/android/codelab-android-compose
Ngoài ra, bạn có thể tải 2 tệp zip xuống:
Xem xét ứng dụng mẫu
Mã bạn vừa tải xuống có chứa mã nguồn dành cho tất cả lớp học lập trình Compose hiện có. Để hoàn tất lớp học lập trình này, hãy mở dự án ThemingCodelab trong Android Studio.
Bạn bên bắt đầu với các đoạn mã trong nhánh main và làm theo hướng dẫn từng bước của lớp học lập trình theo tốc độ của bản thân. Bạn có thể chạy một trong hai phiên bản trong Android Studio bất cứ lúc nào bằng cách thay đổi nhánh git của dự án.
Khám phá mã nguồn khởi đầu
Đoạn mã chính có chứa gói giao diện người dùng, bạn sẽ tương tác với gói và tệp sau đây có trong gói giao diện người dùng này:
MainActivity.kt
– Hoạt động chạy đầu tiên khi bạn khởi chạy ứng dụng Reply.com.example.reply.ui.theme
– Gói này có chứa giao diện, kiểu chữ và bảng phối màu. Bạn sẽ thêm giao diện Material tuỳ chỉnh vào gói này.com.example.reply.ui.components
– Chứa các thành phần tuỳ chỉnh của ứng dụng như Thành phần trong danh sách, Thanh ứng dụng, v.v. Bạn sẽ áp dụng giao diện cho các thành phần này.ReplyApp.kt
– Đây là hàm có khả năng kết hợp chính mà cây giao diện người dùng bắt đầu. Bạn sẽ áp dụng giao diện có cấp cao nhất trong tệp này.
Lớp học lập trình này sẽ tập trung vào các tệp chứa gói ui
.
3. Tuỳ chỉnh giao diện Material 3
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ẻ, Công tắc, v.v.) được xây dựng dựa trên tính năng Tuỳ chỉnh giao diện Material để tuỳ chỉnh Material Design, nhằm phản ánh thương hiệu của sản phẩm hiệu quả hơn và có hệ thống.
Giao diện Material 3 bao gồm các hệ thống con sau đây để thêm giao diện vào ứng dụng: bảng phối màu, kiểu chữ và hình dạng. Khi bạn tuỳ chỉnh các giá trị này, thay đổi sẽ tự động phản ánh trong các thành phần M3 mà bạn sử dụng để xây dựng ứng dụng. Hãy tìm kiểu từng hệ thống con và cách triển khai những hệ thống này trong ứng dụng mẫu.
Hệ thống con của Material 3 gồm có màu sắc, kiểu chữ và hình dạng.
4. Bảng phối màu
Nền tảng của bảng phối màu là bộ 5 màu chủ đạo liên quan đến bảng sắc độ (tonal palette) gồm có 13 tông màu dùng cho các thành phần của Material 3.
5 màu cơ sở chính để tạo giao diện M3.
Để rồi đưa ra 4 màu tương thích với sắc độ khác nhau dựa trên màu nhấn (chính, phụ và trung gian) để phối, xác định điểm nhấn và thể hiện thiết kế trực quan.
4 sắc độ màu phối theo màu nhấn chính, phụ và trung gian.
Tương tự, màu trung tính cũng được chia thành 4 sắc độ màu tương thích dùng cho vùng hiển thị và nền. Các phần này cũng quan trọng để làm nổi bật biểu tượng văn bản khi được đặt trên bất kỳ vùng hiển thị nào.
4 sắc độ màu của màu trung tính cơ sở.
Đọc thêm về Bảng phối màu và vai trò của màu.
Tạo bảng phối màu
Mặc dù bạn có thể tạo ColorScheme
tuỳ chỉnh theo cách thủ công, nhưng thường thì việc tạo một bảng phối màu từ màu thương hiệu sẽ dễ dàng hơn. Công cụ tạo giao diện Material giúp bạn làm điều này cùng với việc xuất mã giao diện Compose (không bắt buộc).
Bạn có thể chọn bất kỳ màu nào bạn thích, nhưng đối với trường hợp sử dụng của chúng tôi, bạn sẽ sử dụng màu chính mặc định của ứng dụng Reply: #825500
. Nhấp vào màu Primary (Màu chính) trong phần Core color (Màu chủ đạo) ở bên trái, rồi thêm đoạn mã vào công cụ chọn màu.
Thêm mã màu chính trong Material Theme Builder.
Sau khi thêm màu chính trong Material Theme Builder (Trình tuỳ chỉnh giao diện Material), bạn sẽ thấy giao diện sau đây và lựa chọn xuất ở góc trên cùng bên phải. Đối với lớp học lập trình này, bạn sẽ xuất giao diện cho Jetpack Compose.
Material Theme Builder (Trình tuỳ chỉnh giao diện Material) có lựa chọn xuất ở góc trên cùng bên phải.
Màu chính #825500
tạo nên giao diện để bạn thêm vào ứng dụng. Material 3 có nhiều vai trò tương ứng với mỗi màu sắc để thể hiện trạng thái, làm nổi bật và tạo điểm nhấn cho thành phần một cách linh hoạt.
Sau khi xuất bảng phối màu sáng và tối từ màu chính.
Tệp do The Color.kt
tạo có chứa màu cho giao diện của bạn, đồng thời xác định tất cả vai trò của màu sắc trên cả giao diện sáng lẫn giao diện tối.
Color.kt
package com.example.reply.ui.theme
import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF825500)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
val md_theme_light_onPrimaryContainer = Color(0xFF291800)
val md_theme_light_secondary = Color(0xFF6F5B40)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFFBDEBC)
val md_theme_light_onSecondaryContainer = Color(0xFF271904)
val md_theme_light_tertiary = Color(0xFF51643F)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFD4EABB)
val md_theme_light_onTertiaryContainer = Color(0xFF102004)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFFFBFF)
val md_theme_light_onBackground = Color(0xFF1F1B16)
val md_theme_light_surface = Color(0xFFFFFBFF)
val md_theme_light_onSurface = Color(0xFF1F1B16)
val md_theme_light_surfaceVariant = Color(0xFFF0E0CF)
val md_theme_light_onSurfaceVariant = Color(0xFF4F4539)
val md_theme_light_outline = Color(0xFF817567)
val md_theme_light_inverseOnSurface = Color(0xFFF9EFE7)
val md_theme_light_inverseSurface = Color(0xFF34302A)
val md_theme_light_inversePrimary = Color(0xFFFFB951)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF825500)
val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFFFFB951)
val md_theme_dark_onPrimary = Color(0xFF452B00)
val md_theme_dark_primaryContainer = Color(0xFF633F00)
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
val md_theme_dark_secondary = Color(0xFFDDC2A1)
val md_theme_dark_onSecondary = Color(0xFF3E2D16)
val md_theme_dark_secondaryContainer = Color(0xFF56442A)
val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
val md_theme_dark_tertiary = Color(0xFFB8CEA1)
val md_theme_dark_onTertiary = Color(0xFF243515)
val md_theme_dark_tertiaryContainer = Color(0xFF3A4C2A)
val md_theme_dark_onTertiaryContainer = Color(0xFFD4EABB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1F1B16)
val md_theme_dark_onBackground = Color(0xFFEAE1D9)
val md_theme_dark_surface = Color(0xFF1F1B16)
val md_theme_dark_onSurface = Color(0xFFEAE1D9)
val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
val md_theme_dark_outline = Color(0xFF9C8F80)
val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
val md_theme_dark_inversePrimary = Color(0xFF825500)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFFFB951)
val md_theme_dark_outlineVariant = Color(0xFF4F4539)
val md_theme_dark_scrim = Color(0xFF000000)
val seed = Color(0xFF825500)
Tệp do The Theme.kt
tạo chứa thông tin thiết lập về bảng phối màu sáng và tối cũng như giao diện của ứng dụng. Tệp này cũng chứa hàm có khả năng kết hợp chính của giao diện AppTheme()
.
Theme.kt
package com.example.reply.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
private val LightColors = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline,
inverseOnSurface = md_theme_light_inverseOnSurface,
inverseSurface = md_theme_light_inverseSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
outlineVariant = md_theme_light_outlineVariant,
scrim = md_theme_light_scrim,
)
private val DarkColors = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inverseSurface = md_theme_dark_inverseSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
)
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
val colors = if (!useDarkTheme) {
LightColors
} else {
DarkColors
}
MaterialTheme(
colorScheme = colors,
content = content
)
}
Phần tử cốt lõi để triển khai giao diện trong Jetpack Compose là thành phần kết hợp MaterialTheme
.
Bạn cần gói thành phần kết hợp MaterialTheme()
trong hàm AppTheme()
, nhận hai tham số:
useDarkTheme
— tham số này gắn liền với hàmisSystemInDarkTheme()
để ghi nhận chế độ cài đặt giao diện của hệ thống và áp dụng giao diện sáng hoặc tối. Nếu muốn ứng dụng dùng giao diện sáng hoặc tối theo cách thủ công, bạn có thể truyền giá trị boolean vàouseDarkTheme
.content
— nội dung áp dụng giao diện này.
Theme.kt
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
val colors = if (!useDarkTheme) {
LightColors
} else {
DarkColors
}
MaterialTheme(
colorScheme = colors,
content = content
)
}
Nếu chạy ứng dụng ngay lúc này, bạn sẽ thấy ứng dụng vẫn hiển thị như cũ. Mặc dù bạn đã nhập bảng phối màu mới với các màu dành cho giao diện khác, bạn vẫn thấy giao diện cơ sở vì bạn chưa áp dụng giao diện này cho ứng dụng Compose.
Ứng dụng dùng giao diện cơ sở khi không áp dụng giao diện nào.
Để áp dụng giao diện mới, trong MainActivity.kt
, hãy gói thành phần kết hợp chính ReplyApp
bằng hàm chính dùng cho giao diện AppTheme()
.
MainActivity.kt
setContent {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
AppTheme {
ReplyApp(/*..*/)
}
}
Bạn cũng sẽ cập nhật các hàm xem trước để xem giao diện này được áp dụng cho bản xem trước ứng dụng. Gói thành phần kết hợp ReplyApp
bên trong ReplyAppPreview()
bằng AppTheme
để áp dụng giao diện cho bản xem trước.
Bạn đã xác định thông số cho bản xem trước dựa trên giao diện sáng lẫn giao diện tối, nên bạn sẽ nhìn thấy bản xem trước cho cả 2 giao diện này.
MainActivity.kt
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "DefaultPreviewDark"
)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_NO,
name = "DefaultPreviewLight"
)
@Composable
fun ReplyAppPreview() {
AppTheme {
ReplyApp(
replyHomeUIState = ReplyHomeUIState(
emails = LocalEmailsDataProvider.allEmails
)
)
}
}
Nếu chạy ứng dụng ngay lúc này, bạn sẽ thấy bản xem trước của ứng dụng có giao diện dùng màu đã nhập thay vì giao diện cơ sở.
Ứng dụng có giao diện cơ sở (Trái).
Ứng dụng có giao diện dùng chủ đề màu sắc đã nhập (Phải).
Bản xem trước có giao diện sáng và tối của ứng dụng dựa trên chủ đề màu sắc đã nhập.
Material 3 giúp tạo cả bảng phối màu sáng lẫn tối. Bạn chỉ việc gói ứng dụng bằng giao diện đã nhập. Các thành phần Material 3 sẽ tự sử dụng vai trò của màu sắc mặc định.
Hãy tìm hiểu về vai trò của màu sắc và cách sử dụng trước khi thêm vào ứng dụng.
Vai trò của màu sắc và khả năng tiếp cận
Mỗi vai trò của màu sắc có thể được sử dụng ở nhiều nơi, tuỳ thuộc vào việc thể hiện trạng thái, làm nổi bật và tạo điểm nhất cho thành phần.
Vai trò của màu sắc tương ứng với màu chính, phụ và màu thứ ba.
Màu chính là màu chủ đạo, dùng cho các thành phần chính như để làm nổi bật các nút và thể hiện trạng thái đang hoạt động.
Màu phụ được dùng cho các thành phần ít nổi bật hơn trên giao diện người dùng, chẳng hạn như cho thẻ bộ lọc.
Màu trung gian được dùng để tạo điểm nhấn tương phản còn màu trung tính được dùng cho nền và vùng hiển thị trong ứng dụng.
Hệ thống màu của Material cung cấp giá trị sắc độ và thông tin đo lường chuẩn đáp ứng được tỷ lệ tương phản. Dùng màu on-primary (màu nền của màu chính) trên màu chính. Dùng màu on-primary-container (màu nền của màu chính cho vùng chứa) trên màu chính cho vùng chứa. Tương tự, hãy phối màu cho các màu nhấn và màu trung tính khác để đáp ứng được tỷ lệ tương phản.
Để biết thêm thông tin, hãy xem vai trò của màu sắc và khả năng tiếp cận.
Độ nâng dùng sắc độ màu và đổ bóng
Material 3 thể hiện độ nâng (elevation) chủ yếu bằng cách sử dụng lớp phủ sắc độ màu. Đây là một cách mới để phân biệt vùng chứa với bề mặt — sử dụng tông màu nổi bật để tăng độ nâng màu (bên cạnh hiệu ứng đổ bóng).
Độ nâng dùng sắc độ màu ở cấp độ 2 dựa trên màu chính.
Lớp phủ độ nâng trong giao diện tối cũng đã thay đổi thành lớp phủ sắc độ màu trong Material Design 3. Màu của lớp phủ lấy dựa trên màu chính.
Surface (vùng hiển thị) của M3 — thành phần kết hợp sao lưu là yếu tố cơ bản của hầu hết các thành phần trong M3 — có độ nâng dựa trên sắc độ và đổ bóng:
Surface(
modifier = modifier,
tonalElevation = {..}
shadowElevation = {..}
) {
Column(content = content)
}
Thêm màu cho ứng dụng
Nếu chạy ứng dụng, bạn có thể thấy các màu đã xuất trên ứng dụng, trong đó thành phần sẽ sử dụng màu mặc định. Chúng ta đã nắm được vai trò và cách sử dụng của màu sắc, hãy cùng thiết kế vai trò của màu sắc cho ứng dụng.
Ứng dụng có giao diện màu và các thành phần có vai trò của màu mặc định.
Màu cho vùng hiển thị
Trên màn hình chính, hãy bắt đầu bằng cách gói thành phần kết hợp chính trong một Surface()
để cung cấp màu nền cho nội dung của ứng dụng. Mở MainActivity.kt
và gói thành phần kết hợp ReplyApp()
bằng Surface
.
Bạn cũng sẽ cung cấp độ nâng dựa trên sắc độ có giá trị 5.dp cho vùng hiển thị có sắc độ dựa trên màu chính, giúp mang lại độ tương phản so với mục danh sách và thanh tìm kiếm phía trên. Theo mặc định, độ nâng dựa trên sắc độ và đổ bóng của bề mặt có giá trị là 0.dp.
MainActivity.kt
AppTheme {
Surface(tonalElevation = 5.dp) {
ReplyApp(
replyHomeUIState = uiState,
// other parameters
)
}
}
Nếu chạy ứng dụng ngay lúc này rồi xem cả Trang danh sách lẫn Trang chi tiết, bạn sẽ thấy sắc độ của vùng hiển thị đã được áp dụng trên toàn bộ ứng dụng.
Nền ứng dụng không dùng màu của vùng hiển thị và nhiều sắc độ màu (Trái).
Nền ứng dụng dùng màu của vùng hiển thị và nhiều sắc độ màu (Phải).
Màu trên thanh ứng dụng
Thanh tìm kiếm tuỳ chỉnh ở phía trên cùng không có màu nền rõ ràng đáp ứng yêu cầu về thiết kế. Theo mặc định, thanh tìm kiếm sẽ quay lại dùng màu nền mặc định. Bạn có thể cung cấp màu nền riêng cho thanh tìm kiếm để phân tách rõ ràng.
Thanh tìm kiếm tuỳ chỉnh không có màu nền riêng (Trái).
Thanh tìm kiếm tuỳ chỉnh có màu nền riêng (Phải).
Bây giờ, bạn cần chỉnh sửa ui/components/ReplyAppBars.kt
chứa thanh ứng dụng. Bạn cần thêm MaterialTheme.colorScheme.background
vào Modifier
của thành phần kết hợp Row
.
ReplyAppBars.kt
@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.background(MaterialTheme.colorScheme.background),
verticalAlignment = Alignment.CenterVertically
) {
// Search bar content
}
}
Giờ đây, bạn sẽ thấy sự khác biệt rõ ràng giữa sắc độ của vùng hiển thị so với thanh ứng dụng khi được dùng màu nền riêng.
Thanh tìm kiếm với màu nền nổi bật.
Màu của nút hành động nổi
FAB lớn không áp dụng chủ đề nào (Trái).
Tuỳ chỉnh giao diện cho FAB lớn bằng màu trung gian (Phải).
Trên màn hình chính, bạn có thể cải thiện giao diện của nút hành động nổi (FAB) để có thể làm nổi bật nút này, khiến nút này trở thành nút kêu gọi hành động. Để triển khai việc này, bạn sẽ áp dụng màu nhấn trung gian cho nút này.
Trong tệp ReplyListContent.kt
, hãy cập nhật containerColor
cho màu FAB thành tertiaryContainer
và màu nội dung thành onTertiaryContainer
để đáp ứng khả năng tiếp cận và độ tương phản về màu sắc.
ReplyListContent.kt
ReplyInboxScreen(/*..*/) {
// Email list content
LargeFloatingActionButton(
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
contentColor = MaterialTheme.colorScheme.onTertiaryContainer
){
/*..*/
}
}
Chạy ứng dụng để xem giao diện FAB của bạn. Trong lớp học lập trình này, bạn sẽ sử dụng LargeFloatingActionButton
.
Màu cho thẻ
Danh sách email trên màn hình chính dùng thành phần thẻ. Theo mặc định, đây là Thẻ có màu, sử dụng màu biến thể của vùng hiển thị làm màu của vùng chứa để phân biệt rõ ràng giữa màu của vùng hiển thị và màu thẻ. Compose cũng cung cấp nhiều cách triển khai cho ElevatedCard
và OutlinedCard
.
Bạn có thể làm nổi bật thêm một số mục quan trọng bằng cách tạo các sắc độ màu thứ cấp. Bạn cần sửa đổi ui/components/ReplyEmailListItem.kt
bằng cách cập nhật màu vùng chứa thẻ và dùng CardDefaults.cardColors()
cho các email quan trọng:
ReplyEmailListItem.kt
Card(
modifier = modifier
.padding(horizontal = 16.dp, vertical = 4.dp)
.semantics { selected = isSelected }
.clickable { navigateToDetail(email.id) },
colors = CardDefaults.cardColors(
containerColor = if (email.isImportant)
MaterialTheme.colorScheme.secondaryContainer
else MaterialTheme.colorScheme.surfaceVariant
)
){
/*..*/
}
Làm nổi bật mục danh sách bằng màu vùng chứa thứ cấp trên sắc độ màu của vùng hiển thị.
Màu chi tiết của mục danh sách
Giờ đây, bạn đã tuỳ chỉnh giao diện cho màn hình chính. Hãy xem trang chi tiết bằng cách nhấp vào bất kỳ mục nào trong danh sách email.
Trang chi tiết mặc định không có mục danh sách được tuỳ chỉnh giao diện (Trái).
Trang chi tiết mặc định có mục danh sách được tuỳ chỉnh giao diện (Phải).
Mục danh sách của bạn không có bất kỳ màu nào để áp dụng, do đó mục này trở lại màu mặc định của vùng hiển thị. Bạn cần áp dụng màu nền cho mục danh sách để phân tách và thêm khoảng đệm nhằm tạo khoảng trống xung quanh nền.
ReplyEmailThreadItem.kt
@Composable
fun ReplyEmailThreadItem(
email: Email,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.background(MaterialTheme.colorScheme.background)
.padding(20.dp)
) {
// List item content
}
}
Dễ nhận thấy chỉ với việc thêm màu nền, sắc độ của vùng hiển thị và mục danh sách đã có sự phân tách rõ ràng.
Giờ đây, cả trang chủ và trang chi tiết đã có vai trò và cách sử dụng màu sắc chính xác. Hãy xem cách ứng dụng của bạn có thể tận dụng màu động để cung cấp trải nghiệm cá nhân hoá và gần gũi hơn cho người dùng.
5. Thêm màu động cho ứng dụng
Màu động là thành phần quan trọng của Material 3, trong đó thuật toán lấy màu sắc tuỳ chỉnh từ hình nền của người dùng để áp dụng cho ứng dụng và giao diện người dùng hệ thống.
Tuỳ chỉnh giao diện động giúp ứng dụng của bạn được cá nhân hoá tốt hơn. Tính năng này cũng mang lại cho người dùng trải nghiệm liền mạch và gần gũi với giao diện của hệ thống.
Màu động hiện có trên phiên bản Android 12 trở lên. Nếu ứng dụng có thể dùng màu động, bạn có thể thiết lập bảng phối màu động bằng cách sử dụng dynamicDarkColorScheme()
hoặc dynamicLightColorScheme()
. Nếu không thể, bạn nên quay lại sử dụng ColorScheme
sáng hoặc tối theo mặc định.
Thay thế mã của hàm AppTheme
trong tệp Theme.kt
bằng hàm bên dưới:
Theme.kt
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val context = LocalContext.current
val colors = when {
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
if (useDarkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
}
useDarkTheme -> DarkColors
else -> LightColors
}
MaterialTheme(
colorScheme = colors,
content = content
)
}
Giao diện động được lấy từ hình nền Android 13.
Khi chạy ứng dụng ngay lúc này, bạn sẽ thấy giao diện động được áp dụng dựa trên hình nền mặc định của Android 13.
Thanh trạng thái cũng nên được định kiểu động tuỳ thuộc vào bảng màu dùng để tuỳ chỉnh giao diện cho ứng dụng của bạn.
Ứng dụng không áp dụng màu cho thanh trạng thái (Trái).
Ứng dụng có áp dụng màu cho thanh trạng thái (Phải).
Để cập nhật màu cho thanh trạng thái dựa trên vào màu chính của giao diện, hãy thêm màu của thanh trạng thái sau bảng phối màu được chọn trong thành phần kết hợp AppTheme
:
Theme.kt
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
// color scheme selection code
// Add primary status bar color from chosen color scheme.
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colors.primary.toArgb()
WindowCompat
.getInsetsController(window, view)
.isAppearanceLightStatusBars = useDarkTheme
}
}
MaterialTheme(
colorScheme = colors,
content = content
)
}
Khi chạy ứng dụng, bạn sẽ thấy thanh trạng thái tuỳ chỉnh dựa theo màu chính. Bạn cũng có thể thử cả giao diện động sáng và tối bằng cách thay đổi giao diện tối của hệ thống.
Chủ đề động sáng (trái) và tối (phải) áp dụng cho hình nền mặc định của Android 13.
Tới đây, bạn đã áp dụng màu sắc để cải thiện giao diện cho ứng dụng. Tuy nhiên, bạn có thể thấy rằng tất cả văn bản trong ứng dụng đều có cùng kích thước, nên giờ là lúc để thêm kiểu chữ vào ứng dụng.
6. Kiểu chữ
Material Design 3 xác định tỷ lệ cỡ chữ. Cách đặt tên và phân nhóm đã được đơn giản hoá thành: hiển thị, dòng tiêu đề, tiêu đề, nội dung và nhãn, với kích thước lớn, trung bình và nhỏ cho mỗi nhóm.
Tỷ lệ cỡ chữ của Material 3.
Xác định kiểu chữ
Compose cung cấp lớp Typography
của M3 – cùng với các lớp TextStyle
và font-related
hiện có – để đặt kiểu chữ mẫu trong Material 3.
Hàm khởi tạo cho Kiểu chữ cung cấp các lựa chọn mặc định tương ứng với 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. Để biết thêm thông tin, hãy xem phần kiểu chữ và giá trị mặc định của kiểu chữ.
Bạn sẽ sử dụng 5 kiểu chữ trong ứng dụng: headlineSmall
, titleLarge
, bodyLarge
, bodyMedium
và labelMedium
. Các kiểu này sẽ hiển thị trên cả màn hình chính và màn hình chi tiết.
Màn hình hiển thị kiểu chữ sử dụng cho tiêu đề, nhãn và nội dung.
Tiếp theo, hãy chuyển đến gói ui/theme
và mở Type.kt
. Thêm mã sau đây để cung cấp cách triển khai của riêng bạn cho một số kiểu văn bản thay thế cho giá trị mặc định:
Type.kt
val typography = Typography(
headlineSmall = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 24.sp,
lineHeight = 32.sp,
letterSpacing = 0.sp
),
titleLarge = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 18.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
bodyLarge = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.15.sp
),
bodyMedium = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.25.sp
),
labelMedium = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
)
Kiểu chữ của bạn hiện đã được xác định. Để thêm kiểu chữ vào giao diện của bạn, hãy chuyển thành phần này vào thành phần kết hợp MaterialTheme()
bên trong AppTheme
:
Theme.kt
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
// dynamic theming content
MaterialTheme(
colorScheme = colors,
typography = typography,
content = content
)
}
Cách dùng kiểu chữ
Giống như màu sắc, bạn có thể dùng thuộc tính MaterialTheme.typography
để biết kiểu chữ cho giao diện hiện tại. Khi thực hiện thao tác này, bạn sẽ nhận được thực thể kiểu chữ để dùng tất cả các kiểu chữ đã xác định trong Type.kt
.
Text(
text = "Hello M3 theming",
style = MaterialTheme.typography.titleLarge
)
Text(
text = "you are learning typography",
style = MaterialTheme.typography.bodyMedium
)
Sản phẩm của bạn có thể sẽ không dùng đến toàn bộ 15 kiểu mặc định trong tỷ lệ kiểu chữ trên Material Design. Trong lớp học lập trình này, bạn chỉ cần dùng 5 kích thước.
Vì bạn chưa áp dụng kiểu chữ nào cho thành phần kết hợp Text()
, nên tất cả văn bản sẽ quay về dùng kiểu chữ Typography.bodyLarge
theo mặc định.
Kiểu chữ dành cho danh sách trên trang chủ
Tiếp theo, hãy áp dụng kiểu chữ cho hàm ReplyEmailListItem
trong ui/components/ReplyEmailListItem.kt
để tạo sự khác biệt giữa tiêu đề và nhãn:
ReplyEmailListItem.kt
Text(
text = email.sender.firstName,
style = MaterialTheme.typography.labelMedium
)
Text(
text = email.createdAt,
style = MaterialTheme.typography.labelMedium
)
Text(
text = email.subject,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)
Text(
text = email.body,
maxLines = 2,
style = MaterialTheme.typography.bodyLarge,
overflow = TextOverflow.Ellipsis
)
Màn hình chính không áp dụng kiểu chữ (Trái).
Màn hình chính có áp dụng kiểu chữ (Phải).
Kiểu chữ dành cho danh sách chi tiết
Tương tự, bạn sẽ thêm kiểu chữ trong màn hình chi tiết bằng cách cập nhật mọi thành phần kết hợp văn bản của ReplyEmailThreadItem
trong ui/components/ReplyEmailThreadItem.kt
:
ReplyEmailThreadItem.kt
Text(
text = email.sender.firstName,
style = MaterialTheme.typography.labelMedium
)
Text(
text = stringResource(id = R.string.twenty_mins_ago),
style = MaterialTheme.typography.labelMedium
)
Text(
text = email.subject,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)
Text(
text = email.body,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Màn hình chi tiết không áp dụng kiểu chữ (Trái).
Màn hình chi tiết có áp dụng kiểu chữ (Phải).
Tuỳ chỉnh kiểu chữ
Với Compose, bạn có thể dễ dàng tuỳ chỉnh kiểu văn bản hoặc tuỳ chỉnh phông chữ. Bạn có thể sửa đổi TextStyle
để tuỳ chỉnh loại phông chữ, bộ phông chữ, khoảng cách chữ cái, v.v.
Bạn cần thay đổi kiểu văn bản trong tệp theme/Type.kt
. Kiểu này sẽ được ánh xạ cho tất cả thành phần sử dụng nó.
Cập nhật fontWeight
thành SemiBold
và lineHeight
thành 32.sp
cho titleLarge
, để dùng cho tiêu đề trong mục danh sách. Việc này sẽ làm nổi bật tiêu đề và phân tách rõ ràng hơn.
Type.kt
...
titleLarge = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 18.sp,
lineHeight = 32.sp,
letterSpacing = 0.0.sp
),
...
Áp dụng kiểu chữ tuỳ chỉnh cho tiêu đề.
7. Hình dạng
Vùng hiển thị của Material có thể hiển thị ở nhiều hình dạng. Hình dạng thường chi phối sự chú ý, cách xác định các thành phần, trạng thái giao tiếp và thể hiện thương hiệu.
Xác định hình dạng
Compose cung cấp cho lớp Shapes
các tham số mở rộng để triển khai hình dạng mới trong M3. Tỷ lệ hình dạng trong M3 (tương tự như tỷ lệ cỡ chữ) có nhiều hình dạng để thể hiện trên giao diện người dùng.
Hình dạng có nhiều kích thước với tỷ lệ hình dạng:
- Quá nhỏ
- Nhỏ
- Trung bình
- Lớn
- Rất lớn
Theo mặc định, mỗi hình dạng có một giá trị mặc định có thể ghi đè. Đối với ứng dụng, bạn sẽ sử dụng hình dạng có kích thước trung bình để sửa đổi mục danh sách. Tuy nhiên, bạn cũng có thể khai báo các hình dạng khác. Tạo tệp mới có tên là Shape.kt
trong gói ui/theme
và thêm mã cho hình dạng:
Shape.kt
package com.example.reply.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp
val shapes = Shapes(
extraSmall = RoundedCornerShape(4.dp),
small = RoundedCornerShape(8.dp),
medium = RoundedCornerShape(16.dp),
large = RoundedCornerShape(24.dp),
extraLarge = RoundedCornerShape(32.dp)
)
Bây giờ, bạn đã xác định được shapes
, hãy truyền nó vào MaterialTheme
của M3 như bạn làm với màu và kiểu chữ:
Theme.kt
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
// dynamic theming content
MaterialTheme(
colorScheme = colors,
typography = typography,
shapes = shapes,
content = content
)
}
Cách dùng hình dạng
Tương tự như màu sắc và kiểu chữ, bạn có thể áp dụng hình dạng cho các thành phần Material bằng cách sử dụng MaterialTheme.shape
. Thành phần này cung cấp cho bạn thực thể Shape
để truy cập vào các hình dạng trong Material.
Nhiều thành phần trong Material đã áp dụng sẵn hình dạng theo mặc định, nhưng bạn có thể cung cấp và áp dụng hình dạng của riêng mình cho các thành phần thông qua các khung có sẵn.
Card(shape = MaterialTheme.shapes.medium) { /* card content */ }
FloatingActionButton(shape = MaterialTheme.shapes.large) { /* fab content */}
Liên kết các thành phần trong Material bằng cách sử dụng nhiều loại hình dạng.
Bạn có thể xem liên kết giữa tất cả thành phần trong phần tài liệu về Hình dạng.
Bạn có thể sử dụng hai hình dạng khác trong Compose: RectangleShape
và CircleShape
. Hình chữ nhật không có bán kính bo góc còn hình tròn hiển thị các cạnh tròn.
Bạn cũng có thể áp dụng hình dạng cho Thành phần bằng Modifiers
có hình dạng, tương tự như Modifier.clip
,Modifier.background vàModifier.border
.
Hình dạng của thanh ứng dụng
Thanh ứng dụng có nền bo tròn các góc:
TopAppBar
đang sử dụng Row
có màu nền. Để bo tròn các góc của nền, hãy xác định hình dạng của nền bằng cách truyền CircleShape
vào đối tượng sửa đổi nền:
ReplyAppBars.kt
@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.background(
MaterialTheme.colorScheme.background,
CircleShape
),
verticalAlignment = Alignment.CenterVertically
) {
// Search bar content
}
}
Hình dạng cho mục trong danh sách chi tiết
Trong màn hình chính, bạn đang sử dụng thẻ với Shape.Medium
theo mặc định. Tuy nhiên, trong trang chi tiết, bạn đã sử dụng một Cột có màu nền. Để giao diện danh sách được đồng bộ, hãy áp dụng hình dạng có kích thước trung bình cho danh sách đó.
Cột chi tiết mục danh sách không có hình dạng riêng ở mục danh sách (Trái) và có hình dạng với kích thước trung bình cho danh sách (Phải).
ReplyEmailThreadItem.kt
@Composable
fun ReplyEmailThreadItem(
email: Email,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(8.dp)
.background(
MaterialTheme.colorScheme.background,
MaterialTheme.shapes.medium
)
.padding(16.dp)
) {
// List item content
}
}
Giờ đây, khi chạy ứng dụng, bạn sẽ thấy một mục danh sách trên màn hình chi tiết có hình dạng medium
.
8. Điểm nhấn
Điểm nhấn trên giao diện người dùng giúp bạn làm nổi bật một số nội dung so với các nội dung khác, chẳng hạn như khi bạn muốn phân biệt tiêu đề với phụ đề. Điểm nhấn trong M3 sử dụng các biến thể màu sắc và sự kết hợp dựa trên màu sắc. Bạn có hai cách để tạo điểm nhấn:
- Sử dụng surface (vùng hiển thị), surface-variant (biến thể của vùng hiển thị) cùng với màu on-surface (màu nền cho vùng hiển thị) và màu on-surface-variants (màu nền cho biến thể của vùng hiển thị) từ hệ thống màu sắc mở rộng của M3.
Ví dụ: có thể sử dụng vùng hiển thị trên màu on-surface-variant và biến thể vùng hiển thị có thể dùng với màu on-surface để tạo các mức độ nổi bật khác nhau.
Các biến thể của vùng hiển thị cũng có thể được sử dụng cùng màu nhấn để làm nổi bật nhưng với mức độ thấp hơn so với màu sắc phù hợp nhưng vẫn đáp ứng khả năng tiếp cận và tỷ lệ tương phản.
Vai trò của màu sắc cho Vùng hiển thị, Nền và Biến thể của vùng hiển thị.
- Sử dụng nhiều độ đậm phông chữ cho văn bản. Như đã thấy trong phần kiểu chữ, bạn có thể cung cấp trọng số tuỳ chỉnh cho kiểu chữ để thể hiện mức độ nhấn khác nhau.
Tiếp theo, hãy cập nhật ReplyEmailListItem.kt
để làm nổi bật với các mức độ khác nhau bằng cách sử dụng biến thể của vùng hiển thị. Theo mặc định, nội dung của thẻ sẽ dùng màu mặc định tuỳ thuộc vào nền.
Bạn cần cập nhật màu của thành phần kết hợp văn bản cho thời gian và cho nội dung thành onSurfaceVariant
. Điều này khiến những nội dung này ít nổi bật hơn so với onContainerColors
(được áp dụng cho các thành phần kết hợp văn bản tiêu đề và tiêu đề theo mặc định).
Thời gian và nội dung cùng mức độ nổi bật so với chủ đề và tiêu đề (Trái).
Thời gian và nội dung ít nổi bật hơn so với tiêu đề và tiêu đề con (Phải).
ReplyEmailListItem.kt
Text(
text = email.createdAt,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = email.body,
maxLines = 2,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
overflow = TextOverflow.Ellipsis
)
Đối với thẻ email quan trọng có nền secondaryContainer
, tất cả màu văn bản đều là màu onSecondaryContainer
theo mặc định. Đối với các email khác, nền là surfaceVariant,
nên tất cả văn bản mặc định là màu onSurfaceVariant
.
9. Xin chúc mừng
Xin chúc mừng! Bạn đã hoàn tất thành công lớp học lập trình này! Bạn đã triển khai tuỳ chỉnh giao diện Material trong Compose bằng cách sử dụng màu, kiểu chữ và hình dạng cùng với màu động để tạo giao diện cho ứng dụng và mang lại trải nghiệm phù hợp.
Kết thúc việc tuỳ chỉnh giao diện sau khi áp dụng chủ đề màu sắc và giao diện màu động.
Các bước tiếp theo
Hãy tham khảo các lớp học lập trình khác của chúng tôi trên Lộ trình học Compose:
- Kiến thức cơ bản về Compose
- Bố cục của Compose
- Trạng thái trong Compose
- Compose cho các ứng dụng hiện có
Tài liệu đọc thêm
Ứng dụng mẫu
- Ứng dụng mẫu Ready với toàn bộ giao diện Material 3 sau khi tuỳ chỉnh
- JetChat minh hoạ việc tuỳ chỉnh giao diện động