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 số. Các thành phần Material (nút, thẻ, công tắc, v.v.) và các bố cục như Scaffold
có sẵn dưới dạng hàm có khả năng kết hợp.
Thành phần Material là các khối xây dựng tương tác để tạo giao diện người dùng. Compose đề xuất một số thành phần ngay từ đầu. Để biết những thành phần đã có sẵn, hãy xem Tài liệu tham khảo API Material trong công cụ Compose.
Thành phần Material sử dụng các giá trị do MaterialTheme
cung cấp trong ứng dụng của bạn:
@Composable
fun MyApp() {
MaterialTheme {
// Material Components like Button, Card, Switch, etc.
}
}
Để tìm hiểu thêm về chủ đề này, hãy xem hướng dẫn về Hệ thống thiết kế trong Compose.
Các vị trí nội dung
Các thành phần Material hỗ trợ nội dung bên trong (nhãn văn bản, biểu tượng, v.v.) có xu hướng cung cấp "các khe" — tức các hàm lambda chung chấp nhận nội dung của thành phần kết hợp — cũng như các hằng số công khai, như kích thước và khoảng đệm, để hỗ trợ sắp xếp nội dung bên trong cho phù hợp với các thông số kỹ thuật của Material.
Ví dụ: đây là Button
:
Button(
onClick = { /* ... */ },
// Uses ButtonDefaults.ContentPadding by default
contentPadding = PaddingValues(
start = 20.dp,
top = 12.dp,
end = 20.dp,
bottom = 12.dp
)
) {
// Inner content including an icon and a text label
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite",
modifier = Modifier.size(ButtonDefaults.IconSize)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("Like")
}
Hình 1. Button
sử dụng khe content
và khoảng đệm mặc định (bên trái) và
Button
sử dụng khe content
để cung cấp một contentPadding
tuỳ chỉnh (bên phải).
Button
có một khe hàm lambda tạo vệt content
chung, sử dụng một RowScope
để sắp xếp bố cục
nội dung các thành phần kết hợp trong một hàng. Phương thức này cũng có một thông số contentPadding
để áp dụng
khoảng đệm cho nội dung bên trong. Bạn có thể sử dụng các hằng số được cung cấp thông qua
ButtonDefaults
, hoặc các giá trị tuỳ chỉnh.
Ví dụ khác là ExtendedFloatingActionButton
:
ExtendedFloatingActionButton(
onClick = { /* ... */ },
icon = {
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite"
)
},
text = { Text("Like") }
)
Hình 2. ExtendedFloatingActionButton
sử dụng các khe icon
và text
.
Thay vì một hàm lambda content
chung, ExtendedFloatingActionButton
có hai
khe dành cho một nhãn icon
và text
. Mặc dù mỗi khe đều hỗ trợ
nội dung thành phần kết hợp chung, nhưng thành phần này vẫn có chủ đích về
cách sắp xếp các nội dung bên trong. Tính năng này xử lý khoảng đệm, căn chỉnh và kích thước
bên trong.
Scaffold
Compose cung cấp các bố cục thuận tiện cho việc kết hợp các thành phần Material vào
các mẫu màn hình phổ biến. Các thành phần kết hợp như Scaffold
cung cấp
các khe cho nhiều loại thành phần và các thành phần màn hình khác.
Nội dung trên màn hình
Scaffold
có một khe hàm lambda tạo vệt content
chung. Hàm lambda nhận một
phiên bản PaddingValues
sẽ được áp dụng cho nội dung gốc — ví dụ: thông qua Modifier.padding
—
để bù vào các thanh trên cùng và dưới cùng, nếu chúng tồn tại.
Scaffold(/* ... */) { contentPadding ->
// Screen content
Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
}
Thanh ứng dụng
Scaffold
cung cấp các khe cho thanh ứng dụng trên cùng hoặc thanh ứng dụng dưới cùng. Vị trí của
các thành phần kết hợp được xử lý nội bộ.
Bạn có thể sử dụng khe topBar
và TopAppBar
:
Scaffold(
topBar = {
TopAppBar { /* Top app bar content */ }
}
) {
// Screen content
}
Bạn có thể sử dụng khe bottomBar
và BottomAppBar
:
Scaffold(
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// Screen content
}
Bạn có thể dùng các khe này cho các thành phần Material khác như BottomNavigation
.
Bạn cũng có thể dùng các thành phần kết hợp tuỳ chỉnh — ví dụ: hãy xem
màn hình giới thiệu
từ mẫu Owl.
Các nút hành động nổi
Scaffold
cung cấp khe cho nút hành động nổi.
Bạn có thể sử dụng khe floatingActionButton
và FloatingActionButton
:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
}
) {
// Screen content
}
Vị trí dưới cùng của thành phần kết hợp FAB được xử lý nội bộ. Bạn có thể sử dụng
thông số floatingActionButtonPosition
để điều chỉnh vị trí
theo chiều ngang:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
// Defaults to FabPosition.End
floatingActionButtonPosition = FabPosition.Center
) {
// Screen content
}
Nếu đang sử dụng khe bottomBar
của thành phần kết hợp Scaffold
, bạn có thể sử dụng
thông số isFloatingActionButtonDocked
để xếp chồng FAB bằng thanh ứng dụng
dưới cùng:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
// Defaults to false
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// Screen content
}
Hình 3. Scaffold
sử dụng khe floatingActionButton
và khe
bottomBar
. Thông số isFloatingActionButtonDocked
được đặt thành
false
(trên cùng) và true
(dưới cùng).
BottomAppBar
hỗ trợ các vết cắt FAB có thông số cutoutShape
,
chấp nhận mọi Shape
. Việc cung cấp cùng một Shape
được thành phần đế sử dụng là một việc đáng thực hiện. Ví dụ:
FloatingActionButton
sử dụng MaterialTheme.shapes.small
với kích thước góc là 50% làm giá trị mặc định cho thông số shape
:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar(
// Defaults to null, that is, No cutout
cutoutShape = MaterialTheme.shapes.small.copy(
CornerSize(percent = 50)
)
) {
/* Bottom app bar content */
}
}
) {
// Screen content
}
Hình 4. Scaffold
có BottomAppBar
và một FloatingActionButton
được gắn vào. BottomAppBar
có một cutoutShape
tuỳ chỉnh
trùng khớp với Shape
được FloatingActionButton
sử dụng.
Các thanh thông báo nhanh
Scaffold
cung cấp một phương tiện để hiển thị thanh thông báo nhanh.
Tính năng này được cung cấp thông qua ScaffoldState
, bao gồm
thuộc tính SnackbarHostState
. Bạn có thể sử dụng
rememberScaffoldState
để tạo một phiên bản của ScaffoldState
. Phiên bản này sẽ được chuyển tới Scaffold
bằng thông số scaffoldState
. SnackbarHostState
cung cấp quyền truy cập
vào hàm
showSnackbar
. Hàm tạm ngưng cần phải có CoroutineScope
— ví dụ:
sử dụng
rememberCoroutineScope
— và có thể được gọi lệnh để phản hồi sự kiện giao diện người dùng nhằm hiển thị
Snackbar
trong Scaffold
.
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
onClick = {
scope.launch {
scaffoldState.snackbarHostState
.showSnackbar("Snackbar")
}
}
)
}
) {
// Screen content
}
Bạn có thể cung cấp một hành động tuỳ chọn và điều chỉnh thời lượng của Snackbar
.
Hàm snackbarHostState.showSnackbar
chấp nhận thông số actionLabel
và duration
bổ sung, và trả về một SnackbarResult
.
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
onClick = {
scope.launch {
val result = scaffoldState.snackbarHostState
.showSnackbar(
message = "Snackbar",
actionLabel = "Action",
// Defaults to SnackbarDuration.Short
duration = SnackbarDuration.Indefinite
)
when (result) {
SnackbarResult.ActionPerformed -> {
/* Handle snackbar action performed */
}
SnackbarResult.Dismissed -> {
/* Handle snackbar dismissed */
}
}
}
}
)
}
) {
// Screen content
}
Bạn có thể cung cấp Snackbar
tuỳ chỉnh với thông số snackbarHost
. Hãy xem
SnackbarHost API reference docs
để
biết thêm thông tin.
Ngăn chứa
Scaffold
cung cấp một khe cho ngăn điều hướng mẫu. Trang tính có thể kéo và bố cục của thành phần kết hợp được xử lý nội bộ.
Bạn có thể sử dụng khe drawerContent
, trong đó sử dụng ColumnScope
để sắp xếp bố cục
các thành phần kết hợp nội dung ngăn chứa trong một cột:
Scaffold(
drawerContent = {
Text("Drawer title", modifier = Modifier.padding(16.dp))
Divider()
// Drawer items
}
) {
// Screen content
}
Scaffold
chấp nhận một số thông số ngăn chứa bổ sung. Ví dụ: bạn có thể
chuyển đổi để xem ngăn chứa có phản hồi với các lệnh kéo bằng thông số
drawerGesturesEnabled
hay không:
Scaffold(
drawerContent = {
// Drawer content
},
// Defaults to true
drawerGesturesEnabled = false
) {
// Screen content
}
Việc lập trình mở và đóng ngăn được thực hiện thông qua ScaffoldState
, trong đó có một
thuộc tính DrawerState
sẽ
được chuyển đếnScaffold
với thông sốscaffoldState
. DrawerState
cung cấp quyền truy cập vào các hàm open
và close
, cũng như
các thuộc tính liên quan đến trạng thái ngăn chứa hiện tại. Các hàm
tạm ngưng này cần phải có CoroutineScope
— ví dụ: sử dụng
rememberCoroutineScope
— và có thể được gọi lệnh để phản hồi các sự kiện giao diện người dùng.
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
// Drawer content
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Open or close drawer") },
onClick = {
scope.launch {
scaffoldState.drawerState.apply {
if (isClosed) open() else close()
}
}
}
)
}
) {
// Screen content
}
Ngăn chứa mẫu
Nếu muốn triển khai ngăn điều hướng mẫu ở chế độ không có Scaffold
, bạn có thể
sử dụng thành phần kết hợp
ModalDrawer
. Thành phần này chấp nhận các thông số ngăn tương tự với Scaffold
.
val drawerState = rememberDrawerState(DrawerValue.Closed)
ModalDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
Nếu muốn triển khai ngăn điều hướng ở dưới cùng, bạn có thể sử dụng
thành phần kết hợp
BottomDrawer
:
val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
BottomDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
Bảng dưới cùng
Nếu muốn triển khai bảng dưới cùng tiêu chuẩn, bạn có thể
sử dụng thành phần kết hợp
BottomSheetScaffold
. Thành phần này chấp nhận các thông số tương tự với Scaffold
, chẳng hạn như topBar
,
floatingActionButton
và snackbarHost
. Tính năng này bao gồm các thông số bổ sung
cung cấp phương tiện để hiển thị các bảng dưới cùng.
Bạn có thể dùng khe sheetContent
. Khe này sử dụng ColumnScope
để sắp xếp bố cục thành phần kết hợp nội dung của bảng trong một cột:
BottomSheetScaffold(
sheetContent = {
// Sheet content
}
) {
// Screen content
}
BottomSheetScaffold
chấp nhận một số thông số bảng tính bổ sung. Ví
dụ: bạn có thể đặt chiều cao xem trước của bảng bằng thông số
sheetPeekHeight
. Bạn cũng có thể chuyển đổi xem ngăn chứa có phản hồi các lệnh kéo bằng
thông số sheetGesturesEnabled
.
BottomSheetScaffold(
sheetContent = {
// Sheet content
},
// Defaults to BottomSheetScaffoldDefaults.SheetPeekHeight
sheetPeekHeight = 128.dp,
// Defaults to true
sheetGesturesEnabled = false
) {
// Screen content
}
Việc mở rộng và thu gọn bảng tính theo lập trình được thực hiện thông qua
BottomSheetScaffoldState
, trong đó có chứa
thuộc tính BottomSheetState
. Bạn
có thể sử dụng rememberBottomSheetScaffoldState
để tạo một phiên bản BottomSheetScaffoldState
sẽ được chuyển cho
BottomSheetScaffold
bằng thông số scaffoldState
. BottomSheetState
cung cấp quyền truy cập vào các hàm expand
và collapse
cũng như các thuộc tính liên quan đến trạng thái hiện tại của bảng tính. Các
hàm tạm ngưng này yêu cầu CoroutineScope
— ví dụ: sử dụng
rememberCoroutineScope
— và có thể được gọi lệnh để phản hồi các sự kiện giao diện người dùng.
val scaffoldState = rememberBottomSheetScaffoldState()
val scope = rememberCoroutineScope()
BottomSheetScaffold(
scaffoldState = scaffoldState,
sheetContent = {
// Sheet content
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Expand or collapse sheet") },
onClick = {
scope.launch {
scaffoldState.bottomSheetState.apply {
if (isCollapsed) expand() else collapse()
}
}
}
)
}
) {
// Screen content
}
Nếu muốn triển khai bảng tính dưới cùng mẫu, bạn có thể sử dụng thành phần kết hợp ModalBottomSheetLayout
:
val sheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.Hidden
)
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
// Sheet content
}
) {
// Screen content
}
Phông nền
Nếu muốn triển khai phông nền, bạn có thể sử dụng thành phần kết hợp
BackdropScaffold
.
BackdropScaffold(
appBar = {
// Top app bar
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
}
)
BackdropScaffold
chấp nhận một số thông số phông nền bổ sung. Ví
dụ: bạn có thể cài đặt chiều cao xem trước của lớp sau và chiều cao
tối thiểu không hoạt động của lớp trước bằng các thông số peekHeight
và headerHeight
.
Bạn cũng có thể bật/tắt chế độ phông nền xem có phản hồi các lệnh kéo bằng thông số
gesturesEnabled
hay không.
BackdropScaffold(
appBar = {
// Top app bar
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
},
// Defaults to BackdropScaffoldDefaults.PeekHeight
peekHeight = 40.dp,
// Defaults to BackdropScaffoldDefaults.HeaderHeight
headerHeight = 60.dp,
// Defaults to true
gesturesEnabled = false
)
Việc công khai và ẩn phông nền có lập trình được thực hiện thông qua
BackdropScaffoldState
. Bạn có thể
sử dụng rememberBackdropScaffoldState
để tạo một phiên bản BackdropScaffoldState
. Phiên bản này nên được chuyển cho
BackdropScaffold
bằng thông số scaffoldState
. BackdropScaffoldState
cung cấp quyền truy cập vào
reveal
và
conceal
, cũng như các thuộc tính liên quan đến trạng thái phông nền hiện tại. Các
hàm tạm ngưng này yêu cầu CoroutineScope
— ví dụ: sử dụng
rememberCoroutineScope
— và có thể được gọi lệnh để phản hồi các sự kiện giao diện người dùng.
val scaffoldState = rememberBackdropScaffoldState(
BackdropValue.Concealed
)
val scope = rememberCoroutineScope()
BackdropScaffold(
scaffoldState = scaffoldState,
appBar = {
TopAppBar(
title = { Text("Backdrop") },
navigationIcon = {
if (scaffoldState.isConcealed) {
IconButton(
onClick = {
scope.launch { scaffoldState.reveal() }
}
) {
Icon(
Icons.Default.Menu,
contentDescription = "Menu"
)
}
} else {
IconButton(
onClick = {
scope.launch { scaffoldState.conceal() }
}
) {
Icon(
Icons.Default.Close,
contentDescription = "Close"
)
}
}
},
elevation = 0.dp,
backgroundColor = Color.Transparent
)
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
}
)