1. Giới thiệu
Compose là một bộ công cụ giao diện người dùng giúp bạn dễ dàng triển khai các thiết kế của ứng dụng. Bạn có thể mô tả giao diện người dùng theo cách bạn muốn, và Compose sẽ xử lý việc vẽ giao diện người dùng trên màn hình. Lớp học lập trình này sẽ hướng dẫn bạn cách viết giao diện người dùng trong Compose. Phần này giả định là bạn đã hiểu các khái niệm được học trong lớp học lập trình cơ bản, vì thế hãy đảm bảo bạn đã hoàn thành lớp học lập trình đó trước. Trong lớp học lập trình về Khái niệm cơ bản, bạn đã tìm hiểu cách triển khai bố cục đơn giản bằng cách sử dụng Surfaces
, Rows
và Columns
. Bạn cũng đã tăng cường những bố cục này bằng các công cụ sửa đổi như padding
, fillMaxWidth
và size
.
Ở lớp học lập trình này, bạn sẽ triển khai một bố cục thực tế và phức tạp hơn, tìm hiểu về nhiều thành phần có thể kết hợp và công cụ sửa đổi độc đáo trong quá trình triển khai. Sau khi kết thúc lớp học lập trình này, bạn sẽ có thể chuyển đổi thiết kế cơ bản của ứng dụng thành những dòng mã hoạt động ổn định.
Lớp học lập trình này không thêm hành vi thực tế nào vào ứng dụng. Thay vào đó, để tìm hiểu về trạng thái và hoạt động tương tác, hãy hoàn thành lớp học lập trình Trạng thái trong Compose.
Để được hỗ trợ thêm khi tham gia lớp học lập trình này, vui lòng xem nội dung các bước tập lập trình bên dưới:
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ách công cụ sửa đổi giúp bạn bổ sung các thành phần kết hợp.
- Cách các thành phần bố cục chuẩn như Column và LazyRow định vị thành phần kết hợp con.
- Cách căn chỉnh và sắp xếp thay đổi vị trí của các thành phần kết hợp con trong thành phần mẹ.
- Cách các thành phần kết hợp Material như Scaffold và Bottom Navigation (Điều hướng dưới cùng) giúp bạn tạo bố cục toàn diện.
- Cách tạo thành phần kết hợp linh hoạt bằng API ô trống (slot API).
- Cách tạo bố cục cho nhiều cấu hình màn hình.
Bạn cần có
- Phiên bản Android Studio mới nhất.
- Kinh nghiệm về cú pháp Kotlin, bao gồm cả lambda.
- Kinh nghiệm cơ bản về Compose. Hãy hoàn thành lớp học lập trình cơ bản về Jetpack Compose nếu bạn chưa thực hiện trước khi bắt đầu lớp học lập trình này.
- Kiến thức cơ bản về thành phần kết hợp và công cụ sửa đổi.
Sản phẩm bạn sẽ tạo ra
Trong lớp học lập trình này, bạn sẽ triển khai một thiết kế ứng dụng thực tế dựa trên các bản mô phỏng do nhà thiết kế cung cấp. MySoothe là một ứng dụng bảo vệ sức khỏe liệt kê nhiều cách để cải thiện cơ thể và tâm trí của bạn. Ứng dụng này có một phần liệt kê các bộ sưu tập yêu thích của bạn và một phần chứa các bài tập thể dục. Giao diện của ứng dụng:
2. Thiết lập
Ở bước này, bạn sẽ tải mã chứa chủ đề và một số chế độ thiết lập cơ bản xuống.
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:
Hãy xem đoạn mã vừa tải
Đoạn mã đã tải xuống chứa mã 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 BasicLayoutsCodelab
trong Android Studio.
Bạn bên bắt đầu bằng 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.
3. Lập kế hoạch trước khi bắt đầu
Chúng ta sẽ bắt đầu bằng cách triển khai thiết kế dọc của ứng dụng – hãy xem xét kỹ hơn:
Khi được yêu cầu triển khai thiết kế, bạn nên bắt đầu bằng cách tìm hiểu rõ cấu trúc của thiết kế đó. Đừng lập trình ngay lập tức mà hãy phân tích chính thiết kế đó. Bạn có thể chia giao diện người dùng này thành nhiều phần có thể sử dụng lại bằng cách nào?
Hãy cùng xem xét thiết kế của chúng tôi nhé. Ở cấp độ trừu tượng cao nhất, chúng ta có thể chia thiết kế này thành hai phần:
- Nội dung của màn hình.
- Thanh điều hướng dưới cùng.
Xem chi tiết, nội dung màn hình chứa 3 phần phụ:
- Thanh Tìm kiếm.
- Một phần có tên là "Căn chỉnh cơ thể".
- Một phần có tên là "Bộ sưu tập yêu thích".
Bên trong mỗi phần, bạn cũng có thể thấy một số thành phần cấp thấp hơn được sử dụng lại:
- Phần tử "điều chỉnh cơ thể" xuất hiện trong một hàng có thể cuộn theo chiều ngang.
- Thẻ "bộ sưu tập yêu thích" xuất hiện trong một lưới có thể cuộn theo chiều ngang.
Sau khi đã phân tích thiết kế, bạn có thể bắt đầu triển khai các thành phần kết hợp cho mọi phần đã xác định trên giao diện người dùng. Bắt đầu với các thành phần kết hợp cấp thấp nhất và tiếp tục kết hợp chúng vào những thành phần kết hợp phức tạp hơn. Khi kết thúc lớp học lập trình này, ứng dụng mới sẽ có dạng như thiết kế được cung cấp.
4. Thanh tìm kiếm – Công cụ sửa đổi
Thành phần đầu tiên để chuyển đổi thành thành phần kết hợp là thanh Tìm kiếm. Hãy cùng xem lại thiết kế:
Nếu chỉ dựa vào ảnh chụp màn hình này, sẽ rất khó để triển khai thiết kế một cách hoàn hảo về mặt điểm ảnh. Nhìn chung, một nhà thiết kế phải truyền tải nhiều thông tin hơn về thiết kế. Họ có thể cấp cho bạn quyền sử dụng công cụ thiết kế hoặc chia sẻ loại thiết kế đường viền màu đỏ. Trong trường hợp này, nhà thiết kế của chúng tôi chuyển giao thiết kế đường viền màu đỏ mà bạn có thể dùng để đọc mọi giá trị kích thước. Thiết kế được hiển thị với lớp phủ lưới 8 dp, do đó bạn có thể dễ dàng thấy khoảng cách giữa các phần tử và xung quanh. Ngoài ra, một số khoảng cách được thêm vào rõ ràng để làm rõ một số kích thước nhất định.
Bạn có thể thấy là thanh tìm kiếm phải có chiều cao là 56 pixel không phụ thuộc vào mật độ. Nó cũng phải lấp đầy chiều rộng của thành phần mẹ.
Để triển khai thanh tìm kiếm, hãy dùng thành phần Material có tên là Trường văn bản. Thư viện Compose Material chứa một thành phần kết hợp được gọi là TextField
. Đây là quá trình triển khai thành phần Material này.
Hãy bắt đầu bằng cách triển khai TextField
cơ bản. Trong cơ sở mã của bạn, mở MainActivity.kt
và tìm thành phần kết hợp SearchBar
.
Bên trong thành phần kết hợp có tên SearchBar
, hãy viết nội dung triển khai TextField
cơ bản:
import androidx.compose.material3.TextField
@Composable
fun SearchBar(
modifier: Modifier = Modifier
) {
TextField(
value = "",
onValueChange = {},
modifier = modifier
)
}
Một số điểm cần lưu ý:
- Bạn đã mã hoá cứng giá trị của trường văn bản và lệnh gọi lại
onValueChange
không thực hiện bất kỳ chức năng nào. Vì đây là lớp học lập trình tập trung vào bố cục, nên vui lòng bỏ qua mọi vấn đề liên quan đến trạng thái.
- Hàm có khả năng kết hợp
SearchBar
chấp nhận tham sốmodifier
và truyền tham số này vàoTextField
. Đây là phương pháp hay nhất theo các nguyên tắc Compose. Điều này cho phép phương thức gọi sửa đổi giao diện của thành phần kết hợp, nhờ đó giao diện linh hoạt hơn và có thể tái sử dụng. Bạn sẽ tiếp tục sử dụng phương pháp hay nhất này đối với tất cả thành phần kết hợp trong lớp học lập trình này.
Hãy xem bản xem trước của thành phần kết hợp này. Nhớ là bạn có thể dùng chức năng Xem trước trong Android Studio để nhanh chóng lặp lại thành phần kết hợp riêng lẻ. MainActivity.kt
chứa bản xem trước tất cả các thành phần kết hợp mà bạn sẽ tạo trong lớp học lập trình này. Trong trường hợp này, phương thức SearchBarPreview
sẽ kết xuất thành phần kết hợp SearchBar
, với một số nền và khoảng đệm để cung cấp thêm ngữ cảnh. Với cách triển khai bạn vừa thêm vào, ứng dụng sẽ có dạng như sau:
Có một số mục bị thiếu. Trước tiên, hãy xác định kích thước của thành phần kết hợp bằng cách sử dụng đối tượng sửa đổi.
Khi viết các thành phần kết hợp, bạn sử dụng công cụ sửa đổi để:
- Thay đổi kích thước, bố cục, hành vi và giao diện của ứng dụng.
- Thêm thông tin, như nhãn hỗ trợ tiếp cận.
- Xử lý dữ liệu do người dùng nhập.
- Thêm các hoạt động tương tác cấp cao, như làm cho một thành phần có thể nhấp, cuộn, kéo hoặc thu phóng được.
Mỗi thành phần kết hợp bạn gọi đều có một tham số modifier
mà bạn có thể thiết lập để điều chỉnh giao diện và hành vi của thành phần kết hợp đó. Khi đặt hệ số sửa đổi, bạn có thể liên kết nhiều phương thức sửa đổi để tạo một phương thức điều chỉnh phức tạp hơn.
Trong trường hợp này, thanh tìm kiếm phải có chiều cao tối thiểu là 56 dp, đồng thời phải lấp đầy chiều rộng của thành phần mẹ. Để tìm đối tượng sửa đổi phù hợp, bạn có thể xem qua danh sách đối tượng sửa đổi và xem mục Kích thước. Bạn có thể sử dụng hệ số sửa đổi heightIn
để biết chiều cao. Việc này đảm bảo thành phần kết hợp có chiều cao tối thiểu cụ thể. Tuy nhiên, thành phần kết hợp này có thể lớn hơn, chẳng hạn như khi người dùng phóng to cỡ chữ hệ thống của mình. Đối với chiều rộng, bạn có thể sử dụng phím bổ trợ fillMaxWidth
. Đối tượng sửa đổi này đảm bảo thanh tìm kiếm sử dụng hết không gian ngang của thành phần mẹ.
Cập nhật công cụ sửa đổi để phù hợp với mã bên dưới:
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.material3.TextField
@Composable
fun SearchBar(
modifier: Modifier = Modifier
) {
TextField(
value = "",
onValueChange = {},
modifier = modifier
.fillMaxWidth()
.heightIn(min = 56.dp)
)
}
Trong trường hợp này, vì một đối tượng sửa đổi ảnh hưởng đến chiều rộng, còn đối tượng khác lại ảnh hưởng đến chiều cao, nên thứ tự của những đối tượng sửa đổi này không quan trọng.
Bạn cũng phải đặt một vài tham số của TextField
. Hãy cố gắng làm cho thành phần kết hợp trông giống như thiết kế bằng cách thiết lập giá trị tham số. Dưới đây là tham chiếu về thiết kế:
Bạn cần thực hiện các bước sau để cập nhật phương thức triển khai:
- Thêm biểu tượng tìm kiếm.
TextField
chứa tham sốleadingIcon
chấp nhận một thành phần kết hợp khác. Bên trong, bạn có thể thiết lậpIcon
. Trong trường hợp này, bạn nên thiết lập biểu tượngSearch
. Hãy nhớ sử dụng đúng lệnh nhậpIcon
của Compose. - Bạn có thể dùng
TextFieldDefaults.textFieldColors
để ghi đè các màu cụ thể. ĐặtfocusedContainerColor
vàunfocusedContainerColor
của trường văn bản thành màusurface
của MaterialTheme. - Thêm văn bản phần giữ chỗ "Tìm kiếm" (bạn có thể tìm thấy văn bản này dưới dạng tài nguyên chuỗi
R.string.placeholder_search
).
Sau khi bạn hoàn tất, thành phần kết hợp sẽ có dạng như sau:
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.ui.res.stringResource
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
@Composable
fun SearchBar(
modifier: Modifier = Modifier
) {
TextField(
value = "",
onValueChange = {},
leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = null
)
},
colors = TextFieldDefaults.colors(
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
focusedContainerColor = MaterialTheme.colorScheme.surface
),
placeholder = {
Text(stringResource(R.string.placeholder_search))
},
modifier = modifier
.fillMaxWidth()
.heightIn(min = 56.dp)
)
}
Lưu ý là:
- Bạn đã thêm
leadingIcon
hiển thị biểu tượng tìm kiếm. Biểu tượng này không cần mô tả nội dung, vì phần giữ chỗ của trường văn bản đã mô tả ý nghĩa của trường văn bản. Hãy nhớ là nội dung mô tả thường được dùng cho các mục đích hỗ trợ tiếp cận, và giúp người dùng ứng dụng thể hiện hình ảnh hoặc biểu tượng bằng văn bản.
- Để điều chỉnh màu nền của trường văn bản, bạn cần đặt thuộc tính
colors
. Thay vì một tham số riêng cho từng màu, thành phần kết hợp chứa một tham số kết hợp. Ở đây, bạn truyền một bản sao của lớp dữ liệuTextFieldDefaults
, trong đó bạn chỉ cập nhật các màu sắc khác nhau. Trong trường hợp này, đó chỉ là màuunfocusedContainerColor
vàfocusedContainerColor
.
Ở bước này, bạn đã biết cách sử dụng các tham số và đối tượng sửa đổi có thể kết hợp để thay đổi giao diện của thành phần kết hợp. Điều này áp dụng cho cả thành phần kết hợp do thư viện Compose và Material cung cấp, cũng như thành phần kết hợp do bạn tự viết. Bạn phải luôn nghĩ đến việc cung cấp các tham số để tuỳ chỉnh thành phần kết hợp mà bạn đang viết. Bạn cũng nên thêm thuộc tính modifier
để có thể điều chỉnh giao diện của thành phần kết hợp từ bên ngoài.
5. Căn chỉnh cơ thể – Căn chỉnh
Thành phần kết hợp tiếp theo mà bạn sẽ triển khai là phần tử "Căn chỉnh cơ thể". Hãy cùng xem thiết kế của SDK, bao gồm cả thiết kế đường viền đỏ bên cạnh thiết kế đó:
Thiết kế đường viền đỏ hiện cũng chứa các khoảng cách theo hướng đường cơ sở. Dưới đây là thông tin mà chúng tôi thu được từ hình ảnh đó:
- Hình ảnh phải cao 88 dp.
- Khoảng cách giữa đường cơ sở của văn bản và hình ảnh phải là 24 dp.
- Khoảng cách giữa đường cơ sở và đáy của phần tử phải là 8 dp.
- Văn bản này phải có kiểu chữ bodyMedium.
Để triển khai thành phần kết hợp này, bạn cần có thành phần kết hợp Image
và Text
. Bạn cần thêm các cột này vào Column
để chúng được đặt bên dưới nhau.
Tìm thành phần kết hợp AlignYourBodyElement
trong mã và cập nhật nội dung của thành phần đó bằng cách triển khai cơ bản sau:
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.ui.res.painterResource
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
) {
Image(
painter = painterResource(R.drawable.ab1_inversions),
contentDescription = null
)
Text(text = stringResource(R.string.ab1_inversions))
}
}
Lưu ý là:
- Bạn đặt
contentDescription
của hình ảnh thành rỗng, vì hình ảnh này chỉ mang tính chất trang trí. Văn bản bên dưới hình ảnh mô tả đủ ý nghĩa, vì vậy hình ảnh không cần thêm nội dung mô tả. - Bạn đang sử dụng hình ảnh và văn bản được cố định giá trị trong mã. Trong bước tiếp theo, bạn sẽ di chuyển các tham số này để sử dụng các tham số được cung cấp trong thành phần kết hợp
AlignYourBodyElement
và biến chúng thành động.
Hãy xem bản xem trước của thành phần kết hợp này:
Chúng tôi cần cải thiện một số điểm. Đáng chú ý nhất là hình ảnh quá lớn và không có hình tròn. Bạn có thể điều chỉnh thành phần kết hợp Image
bằng các hệ số sửa đổi size
và clip
và tham số contentScale
.
Đối tượng sửa đổi size
điều chỉnh thành phần kết hợp cho phù hợp với một kích thước cụ thể, tương tự như đối tượng sửa đổi fillMaxWidth
và heightIn
mà bạn đã thấy ở bước trước. Công cụ sửa đổi clip
hoạt động theo cách khác và điều chỉnh giao diện của thành phần kết hợp. Bạn có thể đặt thuộc tính này thành bất kỳ Shape
nào, và nó sẽ cắt nội dung của thành phần kết hợp thành hình dạng đó.
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.draw.clip
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
) {
Image(
painter = painterResource(R.drawable.ab1_inversions),
contentDescription = null,
modifier = Modifier
.size(88.dp)
.clip(CircleShape)
)
Text(text = stringResource(R.string.ab1_inversions))
}
}
Hiện tại, thiết kế của bạn trong Bản xem trước sẽ có dạng như sau:
Hình ảnh cũng cần được điều chỉnh theo tỷ lệ chính xác. Để làm việc này, chúng ta có thể dùng tham số contentScale
của Image
. Có một vài lựa chọn, đáng chú ý nhất trong số đó là:
Trong trường hợp này, loại ảnh cắt chính là loại cần sử dụng. Sau khi áp dụng đối tượng sửa đổi và tham số, mã của bạn sẽ có dạng như sau:
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
) {
Image(
painter = painterResource(R.drawable.ab1_inversions),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(88.dp)
.clip(CircleShape)
)
Text( text = stringResource(R.string.ab1_inversions) )
}
}
Tệp của bạn hiện sẽ có dạng như sau:
Bước tiếp theo, hãy căn chỉnh văn bản theo chiều ngang bằng cách đặt thuộc tính căn chỉnh của Column
.
Nhìn chung, để căn chỉnh các thành phần kết hợp bên trong một vùng chứa gốc, hãy thiết lập chỉ số căn chỉnh của vùng chứa gốc đó. Do đó, thay vì yêu cầu thành phần con tự đặt mình vào vị trí gốc, bạn sẽ yêu cầu cha mẹ cách căn chỉnh vị trí con của mình.
Đối với Column
, bạn có thể quyết định cách căn chỉnh phần tử con theo chiều ngang. Có các lựa chọn sau:
- Bắt đầu
- Căn giữa theo chiều ngang
- Kết thúc
Đối với Row
, bạn hãy đặt cách căn chỉnh dọc. Các tuỳ chọn tương tự như các tuỳ chọn của Column
:
- Trên cùng
- Căn giữa theo chiều dọc
- Dưới cùng
Đối với Box
, bạn sẽ kết hợp cả căn chỉnh ngang và dọc. Có các lựa chọn sau:
- TopStart
- TopCenter
- TopEnd
- CenterStart
- Center
- CenterEnd
- BottomStart
- BottomCenter
- BottomEnd
Tất cả các phần tử con của vùng chứa sẽ tuân theo cùng một mẫu căn chỉnh này. Bạn có thể ghi đè hành vi của một thành phần con bằng cách thêm phím bổ trợ align
vào thành phần đó.
Đối với thiết kế này, văn bản phải được căn giữa theo chiều ngang. Để làm việc đó, hãy đặt horizontalAlignment
của Column
ở giữa theo chiều ngang:
import androidx.compose.ui.Alignment
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
) {
Image(
//..
)
Text(
//..
)
}
}
Sau khi triển khai các phần này, bạn chỉ cần thực hiện một số thay đổi nhỏ để đảm bảo thành phần kết hợp giống với thiết kế. Hãy cố gắng tự triển khai các mã này hoặc tham khảo mã cuối cùng nếu bạn gặp khó khăn. Hãy thử thực hiện các bước sau:
- Tạo hình ảnh và văn bản động. Truyền chúng dưới dạng đối số vào hàm có khả năng kết hợp. Đừng quên cập nhật Bản xem trước tương ứng và truyền một số dữ liệu được cố định giá trị trong mã.
- Cập nhật văn bản để dùng kiểu chữ bodyMedium.
- Cập nhật khoảng cách đường cơ sở của phần tử văn bản theo biểu đồ.
Sau khi hoàn tất các bước này, mã của bạn sẽ trông giống như sau:
import androidx.compose.foundation.layout.paddingFromBaseline
import androidx.compose.ui.Alignment
import androidx.compose.ui.layout.ContentScale
@Composable
fun AlignYourBodyElement(
@DrawableRes drawable: Int,
@StringRes text: Int,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(88.dp)
.clip(CircleShape)
)
Text(
text = stringResource(text),
modifier = Modifier.paddingFromBaseline(top = 24.dp, bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
}
}
@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE)
@Composable
fun AlignYourBodyElementPreview() {
MySootheTheme {
AlignYourBodyElement(
text = R.string.ab1_inversions,
drawable = R.drawable.ab1_inversions,
modifier = Modifier.padding(8.dp)
)
}
}
Hãy xem AlignYourBodyElement trong tab Thiết kế.
6. Thẻ "bộ sưu tập yêu thích" – Material Surface
Thành phần kết hợp tiếp theo cần triển khai tương tự như phần tử "Căn chỉnh nội dung". Dưới đây là thiết kế, bao gồm cả các đường viền đỏ:
Trong trường hợp này, kích thước đầy đủ của thành phần kết hợp được cung cấp. Bạn có thể thấy rằng văn bản sẽ là titleMedium.
Vùng chứa này dùng surfaceVariant làm màu nền và khác với màu nền của toàn bộ màn hình. Nó cũng có các góc bo tròn. Chúng ta chỉ định các thông tin này cho thẻ bộ sưu tập yêu thích bằng cách sử dụng thành phần kết hợp Surface
của Material.
Bạn có thể điều chỉnh Surface
cho phù hợp với nhu cầu của mình, bằng cách đặt các tham số và hệ số sửa đổi. Trong trường hợp này, bề mặt phải có các góc tròn. Bạn có thể sử dụng tham số shape
cho trường hợp này. Thay vì đặt hình dạng thành Shape
như đối với Hình ảnh trong bước trước, bạn sẽ sử dụng giá trị lấy từ giao diện Material.
Hãy cùng xem nhé:
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Surface
@Composable
fun FavoriteCollectionCard(
modifier: Modifier = Modifier
) {
Surface(
shape = MaterialTheme.shapes.medium,
modifier = modifier
) {
Row {
Image(
painter = painterResource(R.drawable.fc2_nature_meditations),
contentDescription = null
)
Text(text = stringResource(R.string.fc2_nature_meditations))
}
}
}
Hãy xem Bản xem trước này:
Tiếp theo, hãy áp dụng các bài học rút ra trong bước trước.
- Đặt chiều rộng của
Row
và căn chỉnh phần tử con theo chiều dọc. - Đặt kích thước hình ảnh theo biểu đồ và cắt ảnh trong vùng chứa của hình ảnh đó.
Hãy cố gắng tự triển khai các thay đổi này trước khi xem xét mã giải pháp!
Mã của bạn bây giờ sẽ có dạng như sau:
import androidx.compose.foundation.layout.width
@Composable
fun FavoriteCollectionCard(
modifier: Modifier = Modifier
) {
Surface(
shape = MaterialTheme.shapes.medium,
modifier = modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.width(255.dp)
) {
Image(
painter = painterResource(R.drawable.fc2_nature_meditations),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(80.dp)
)
Text(
text = stringResource(R.string.fc2_nature_meditations)
)
}
}
}
Bản xem trước hiện sẽ có dạng như sau:
Để hoàn tất thành phần kết hợp này, hãy triển khai các bước sau:
- Tạo hình ảnh và văn bản động. Truyền chúng cho hàm có khả năng kết hợp dưới dạng đối số.
- Cập nhật màu sắc thành surfaceVariant.
- Cập nhật văn bản để dùng kiểu chữ titleMedium.
- Cập nhật khoảng cách giữa hình ảnh và văn bản.
Kết quả cuối cùng sẽ có dạng như sau:
@Composable
fun FavoriteCollectionCard(
@DrawableRes drawable: Int,
@StringRes text: Int,
modifier: Modifier = Modifier
) {
Surface(
shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colorScheme.surfaceVariant,
modifier = modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.width(255.dp)
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(80.dp)
)
Text(
text = stringResource(text),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(horizontal = 16.dp)
)
}
}
}
//..
@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE)
@Composable
fun FavoriteCollectionCardPreview() {
MySootheTheme {
FavoriteCollectionCard(
text = R.string.fc2_nature_meditations,
drawable = R.drawable.fc2_nature_meditations,
modifier = Modifier.padding(8.dp)
)
}
}
Xem Bản xem trước của FavoriteCollectionCardPreview.
7. Hàng "căn chỉnh cơ thể" – Sắp xếp
Sau khi tạo các thành phần kết hợp cơ bản xuất hiện trên màn hình, bạn có thể bắt đầu tạo các phần khác nhau cho màn hình.
Bắt đầu từ hàng có thể cuộn là "Điều chỉnh cơ thể".
Dưới đây là thiết kế của đường viền đỏ cho thành phần này:
Hãy nhớ là một khối của lưới đại diện cho 8 dp. Vì vậy, trong thiết kế này có khoảng trống 16 dp trước mục đầu tiên và sau mục cuối cùng trong hàng. Có 8dp khoảng cách giữa mỗi mục.
Trong tính năng Compose, bạn có thể triển khai một hàng có thể cuộn như thế này bằng cách sử dụng thành phần kết hợp LazyRow
. Tài liệu về danh sách chứa nhiều thông tin hơn các danh sách Lazy như LazyRow
và LazyColumn
. Đối với lớp học lập trình này, bạn chỉ cần biết rằng LazyRow
chỉ cho thấy các thành phần hiển thị trên màn hình (thay vì tất cả các thành phần cùng một lúc) để giúp ứng dụng hoạt động hiệu quả.
Bắt đầu với cách triển khai cơ bản của LazyRow
này:
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
@Composable
fun AlignYourBodyRow(
modifier: Modifier = Modifier
) {
LazyRow(
modifier = modifier
) {
items(alignYourBodyData) { item ->
AlignYourBodyElement(item.drawable, item.text)
}
}
}
Như bạn có thể thấy, thành phần con của LazyRow
không phải là thành phần kết hợp. Thay vào đó, bạn sẽ sử dụng DSL danh sách Lazy để cung cấp các phương thức như item
và items
phát ra các thành phần kết hợp dưới dạng mục danh sách. Đối với mỗi mục trong alignYourBodyData
được cung cấp, bạn sẽ phát hành một thành phần kết hợp AlignYourBodyElement
đã triển khai trước đó.
Lưu ý cách hiển thị này:
Các khoảng cách mà chúng tôi thấy trong thiết kế đường viền đỏ vẫn bị thiếu. Để triển khai những tính năng này, bạn phải tìm hiểu về cách sắp xếp.
Trong bước trước, bạn đã tìm hiểu về cách căn chỉnh, dùng để căn chỉnh phần tử con của một vùng chứa trên Trục chéo. Đối với Column
, trục chéo là trục hoành, còn đối với Row
, trục chéo là trục tung.
Tuy nhiên, chúng ta cũng có thể quyết định cách đặt các thành phần kết hợp con trên Trục chính của vùng chứa (ngang với Row
, dọc) của Column
.
Đối với Row
, bạn có thể chọn những cách sắp xếp sau:
Và với Column
:
Ngoài những cách sắp xếp này, bạn cũng có thể dùng phương thức Arrangement.spacedBy()
để thêm một khoảng trống cố định giữa mỗi thành phần kết hợp con.
Trong ví dụ này, phương thức spacedBy
là phương thức bạn cần sử dụng, vì bạn muốn đặt khoảng cách 8 dp giữa mỗi mục trong LazyRow
.
import androidx.compose.foundation.layout.Arrangement
@Composable
fun AlignYourBodyRow(
modifier: Modifier = Modifier
) {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier
) {
items(alignYourBodyData) { item ->
AlignYourBodyElement(item.drawable, item.text)
}
}
}
Thiết kế hiện sẽ có dạng như sau:
Bạn cũng cần thêm một số khoảng đệm ở các cạnh của LazyRow
. Trong trường hợp này, việc thêm một đối tượng sửa đổi khoảng đệm đơn giản sẽ không có tác dụng. Hãy thử thêm khoảng đệm vào LazyRow
và xem hành vi của nó bằng cách sử dụng bản xem trước tương tác:
Như bạn có thể thấy, khi cuộn, mục đầu tiên và mục hiển thị cuối cùng bị cắt ở cả hai bên màn hình.
Để duy trì cùng một khoảng đệm nhưng vẫn cuộn nội dung trong giới hạn của danh sách gốc mà không cắt đoạn, tất cả danh sách đều phải cung cấp một tham số cho LazyRow
có tên là contentPadding
và đặt tham số đó thành 16.dp
.
import androidx.compose.foundation.layout.PaddingValues
@Composable
fun AlignYourBodyRow(
modifier: Modifier = Modifier
) {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(horizontal = 16.dp),
modifier = modifier
) {
items(alignYourBodyData) { item ->
AlignYourBodyElement(item.drawable, item.text)
}
}
}
Dùng thử bản xem trước tương tác để xem khoảng đệm tạo ra sự khác biệt như thế nào.
8. Lưới "bộ sưu tập yêu thích" – Lưới lazy
Phần tiếp theo cần triển khai là phần "Bộ sưu tập yêu thích" trên màn hình. Thành phần kết hợp này cần một lưới thay vì một hàng:
Bạn có thể triển khai phần này tương tự như phần trước, bằng cách tạo LazyRow
rồi cho phép mỗi mục giữ một Column
có 2 thực thể FavoriteCollectionCard
. Tuy nhiên, trong bước này, bạn sẽ sử dụng LazyHorizontalGrid
để cung cấp ánh xạ đẹp hơn từ các mục đến các phần tử lưới.
Bắt đầu với cách triển khai lưới đơn giản bằng hai hàng cố định:
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.items
@Composable
fun FavoriteCollectionsGrid(
modifier: Modifier = Modifier
) {
LazyHorizontalGrid(
rows = GridCells.Fixed(2),
modifier = modifier
) {
items(favoriteCollectionsData) { item ->
FavoriteCollectionCard(item.drawable, item.text)
}
}
}
Như bạn có thể thấy, chỉ cần thay thế LazyRow
từ bước trước bằng LazyHorizontalGrid
. Tuy nhiên, việc này cũng chưa cung cấp cho bạn kết quả chính xác:
Lưới chiếm nhiều không gian hơn thành phần chính, có nghĩa là các thẻ bộ sưu tập yêu thích sẽ bị kéo giãn quá nhiều theo chiều dọc.
Điều chỉnh thành phần kết hợp để
- Lưới có contentPadding ngang là 16 dp.
- Cách sắp xếp theo chiều ngang và chiều dọc có khoảng cách là 16 dp.
- Chiều cao của lưới là 168 dp.
- Đối tượng sửa đổi của FavoriteCollectionCard chỉ định chiều cao là 80 dp.
Mã hoàn thiện sẽ có dạng như sau:
@Composable
fun FavoriteCollectionsGrid(
modifier: Modifier = Modifier
) {
LazyHorizontalGrid(
rows = GridCells.Fixed(2),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier.height(168.dp)
) {
items(favoriteCollectionsData) { item ->
FavoriteCollectionCard(item.drawable, item.text, Modifier.height(80.dp))
}
}
}
Bản xem trước sẽ có dạng như sau:
9. Phần trang chủ - API dạng ô trống (Slot APIs)
Trong màn hình chính của MySoothe, có nhiều phần tuân theo cùng một mẫu. Mỗi phần đều có tiêu đề và một số nội dung thay đổi tuỳ theo phần đó. Dưới đây là thiết kế của đường viền đỏ mà chúng tôi muốn triển khai:
Như bạn có thể thấy, mỗi phần có một tiêu đề và một ô trống. Tiêu đề chứa một vài thông tin về khoảng cách và kiểu liên kết với tiêu đề đó. Ô trống này có thể được tự động điền nhiều nội dung, tuỳ thuộc vào từng phần.
Để triển khai vùng chứa phần linh hoạt này, bạn hãy sử dụng API khe. Trước khi bạn triển khai phương thức này, vui lòng đọc phần trên trang tài liệu về bố cục dựa trên khe. Phần này sẽ giúp bạn hiểu bố cục theo ô trống là gì và cách bạn có thể sử dụng API ô trống (slot API) để tạo một bố cục như vậy.
Điều chỉnh thành phần kết hợp HomeSection
để nhận nội dung tiêu đề và vị trí. Bạn cũng nên điều chỉnh Bản xem trước liên kết để gọi HomeSection
này bằng tiêu đề và nội dung "Căn chỉnh cơ thể":
@Composable
fun HomeSection(
@StringRes title: Int,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Column(modifier) {
Text(stringResource(title))
content()
}
}
@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE)
@Composable
fun HomeSectionPreview() {
MySootheTheme {
HomeSection(R.string.align_your_body) {
AlignYourBodyRow()
}
}
}
Bạn có thể sử dụng tham số content
cho khe của thành phần kết hợp. Bằng cách này, khi sử dụng thành phần kết hợp HomeSection
, bạn có thể sử dụng trailing lambda (lambda theo sau) để lấp đầy ô trống nội dung. Khi một thành phần kết hợp cung cấp nhiều ô trống để điền vào, bạn có thể đặt cho chúng những cái tên ý nghĩa, đại diện cho hàm của chúng trong vùng chứa có thể kết hợp lớn hơn. Chẳng hạn như TopAppBar
của Material cung cấp các khe cho title
, navigationIcon
và actions
.
Hãy xem cách triển khai phần này:
Thành phần kết hợp Text (Văn bản) cần thêm một vài thông tin để khớp với thiết kế.
Hãy cập nhật để:
- Hệ thống sử dụng kiểu chữ titleMedium.
- Khoảng cách giữa đường cơ sở của văn bản và phần trên cùng là 40 dp.
- Khoảng cách giữa đường cơ sở và phần dưới cùng của phần tử là 16 dp.
- Khoảng đệm ngang là 16 dp.
Giải pháp cuối cùng của bạn sẽ có dạng như sau:
@Composable
fun HomeSection(
@StringRes title: Int,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Column(modifier) {
Text(
text = stringResource(title),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier
.paddingFromBaseline(top = 40.dp, bottom = 16.dp)
.padding(horizontal = 16.dp)
)
content()
}
}
10. Màn hình chính - Cuộn
Giờ thì bạn đã tạo tất cả thành phần riêng biệt, bạn có thể kết hợp chúng thành thiết kế triển khai toàn màn hình.
Dưới đây là thiết kế bạn đang muốn triển khai:
Chúng ta chỉ cần đặt thanh tìm kiếm và 2 phần này bên dưới nhau. Bạn cần thêm một số khoảng trống để mọi thứ có thể phù hợp với thiết kế này. Một thành phần kết hợp mà chúng ta chưa từng sử dụng trước đây là Spacer
, giúp đặt thêm chỗ trống bên trong Column
. Thay vào đó, nếu đặt khoảng đệm của Column
, bạn sẽ nhận được hành vi cắt bỏ đã thấy trước đây trong lưới Bộ sưu tập yêu thích.
@Composable
fun HomeScreen(modifier: Modifier = Modifier) {
Column(modifier) {
Spacer(Modifier.height(16.dp))
SearchBar(Modifier.padding(horizontal = 16.dp))
HomeSection(title = R.string.align_your_body) {
AlignYourBodyRow()
}
HomeSection(title = R.string.favorite_collections) {
FavoriteCollectionsGrid()
}
Spacer(Modifier.height(16.dp))
}
}
Mặc dù thiết kế vừa với hầu hết các kích thước thiết bị, nhưng bạn cần phải cuộn được theo chiều dọc trong trường hợp thiết bị không đủ cao – ví dụ như ở chế độ ngang. Để làm được điều đó, bạn phải thêm hành vi cuộn.
Như chúng ta đã thấy, các bố cục Lazy, chẳng hạn như LazyRow
và LazyHorizontalGrid
sẽ tự động thêm hành vi cuộn. Tuy nhiên, không phải lúc nào bạn cũng cần bố cục Lazy. Nhìn chung, bạn sẽ sử dụng bố cục Lazy khi có nhiều phần tử trong danh sách hoặc tập dữ liệu lớn để tải, vậy nên việc phát hành tất cả các mục cùng lúc sẽ dẫn đến chi phí hiệu suất và làm chậm ứng dụng của bạn. Khi một danh sách chỉ có số lượng phần tử hạn chế, bạn có thể chọn sử dụng Column
hoặc Row
đơn giản rồi thêm hành vi cuộn theo cách thủ công. Để thực hiện việc này, bạn cần sử dụng công cụ sửa đổi verticalScroll
hoặc horizontalScroll
. Phương thức này yêu cầu phải có ScrollState
, chứa trạng thái hiện tại của thao tác cuộn dùng để sửa đổi trạng thái cuộn từ bên ngoài. Trong trường hợp này, bạn không muốn sửa đổi trạng thái cuộn, do đó chỉ cần tạo một phiên bản ScrollState
cố định bằng cách sử dụng rememberScrollState
.
Kết quả cuối cùng sẽ có dạng như sau:
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@Composable
fun HomeScreen(modifier: Modifier = Modifier) {
Column(
modifier
.verticalScroll(rememberScrollState())
) {
Spacer(Modifier.height(16.dp))
SearchBar(Modifier.padding(horizontal = 16.dp))
HomeSection(title = R.string.align_your_body) {
AlignYourBodyRow()
}
HomeSection(title = R.string.favorite_collections) {
FavoriteCollectionsGrid()
}
Spacer(Modifier.height(16.dp))
}
}
Để xác minh hành vi cuộn của thành phần kết hợp, hãy giới hạn chiều cao của Bản xem trước và chạy trong bản xem trước tương tác:
@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE, heightDp = 180)
@Composable
fun ScreenContentPreview() {
MySootheTheme { HomeScreen() }
}
11. Thanh điều hướng dưới cùng – Material
Bây giờ, sau khi triển khai nội dung trên màn hình, bạn đã sẵn sàng thêm trang trí cửa sổ. Trong trường hợp của MySoothe, có một thanh điều hướng cho phép người dùng chuyển đổi giữa các màn hình.
Trước tiên, hãy triển khai thành phần kết hợp thanh điều hướng, sau đó đưa vào ứng dụng của bạn.
Hãy cùng xem thiết kế nhé:
Rất may là bạn không phải tự triển khai toàn bộ thành phần kết hợp này. Bạn có thể dùng thành phần kết hợp NavigationBar
thuộc thư viện Compose Material. Bên trong thành phần kết hợp NavigationBar
, bạn có thể thêm một hoặc nhiều phần tử NavigationBarItem
. Sau đó, thư viện Material sẽ tự động tạo kiểu cho các phần tử đó.
Bắt đầu từ cách triển khai cơ bản của thanh điều hướng dưới cùng này:
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Spa
@Composable
private fun SootheBottomNavigation(modifier: Modifier = Modifier) {
NavigationBar(
modifier = modifier
) {
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.Spa,
contentDescription = null
)
},
label = {
Text(
text = stringResource(R.string.bottom_navigation_home)
)
},
selected = true,
onClick = {}
)
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(
text = stringResource(R.string.bottom_navigation_profile)
)
},
selected = false,
onClick = {}
)
}
}
Đây là giao diện triển khai cơ bản – không có nhiều sự tương phản giữa màu nội dung và màu của thanh điều hướng.
Bạn nên điều chỉnh một số kiểu. Trước hết, bạn có thể cập nhật màu nền của thanh điều hướng dưới cùng bằng cách đặt tham số containerColor
của thanh điều hướng đó. Bạn có thể dùng màu surfaceVariant trên giao diện Material cho việc này. Giải pháp cuối cùng của bạn sẽ có dạng như sau:
@Composable
private fun SootheBottomNavigation(modifier: Modifier = Modifier) {
NavigationBar(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
modifier = modifier
) {
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.Spa,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_home))
},
selected = true,
onClick = {}
)
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_profile))
},
selected = false,
onClick = {}
)
}
}
Giờ thì thanh điều hướng sẽ có dạng như thế này, hãy chú ý đến cách thanh điều hướng cung cấp độ tương phản cao hơn.
12. Ứng dụng MySoothe – Scaffold
Đối với bước này, hãy tạo phương thức triển khai toàn màn hình, bao gồm cả thanh điều hướng dưới cùng. Sử dụng thành phần kết hợp Scaffold
của Material. Scaffold
cung cấp cho bạn thành phần kết hợp có thể định cấu hình cấp cao nhất đối với các ứng dụng triển khai Material Design. Thành phần này chứa các khe cho nhiều khái niệm Material, trong đó có một thành phần là thanh dưới cùng. Trong thanh dưới cùng này, bạn có thể đặt thành phần kết hợp điều hướng dưới cùng đã tạo ở bước trước.
Triển khai thành phần kết hợp MySootheAppPortrait()
. Đây là thành phần kết hợp cấp cao nhất dành cho ứng dụng, do đó bạn nên:
- Áp dụng giao diện Material
MySootheTheme
. - Thêm biến
Scaffold
. - Đặt thanh dưới cùng thành thành phần kết hợp
SootheBottomNavigation
. - Thiết lập nội dung thành thành phần kết hợp
HomeScreen
.
Kết quả cuối cùng của bạn sẽ là:
import androidx.compose.material3.Scaffold
@Composable
fun MySootheAppPortrait() {
MySootheTheme {
Scaffold(
bottomBar = { SootheBottomNavigation() }
) { padding ->
HomeScreen(Modifier.padding(padding))
}
}
}
Đã hoàn tất quá trình triển khai! Nếu muốn kiểm tra xem phiên bản của mình có được triển khai theo cách hoàn hảo trong một pixel hay không, bạn có thể so sánh hình ảnh này với Bản xem trước triển khai của riêng bạn.
13. Dải điều hướng – Material
Khi tạo bố cục cho các ứng dụng, bạn cũng cần lưu ý đến giao diện của ứng dụng trong nhiều cấu hình, bao gồm cả chế độ ngang trên điện thoại. Đây là thiết kế của ứng dụng ở chế độ ngang, hãy chú ý đến cách thanh điều hướng dưới cùng chuyển thành một dải ở bên trái của nội dung trên màn hình.
Để triển khai việc này, bạn sẽ dùng thành phần kết hợp NavigationRail
thuộc thư viện Compose Material và có cách triển khai tương tự như NavigationBar
dùng để tạo thanh điều hướng dưới cùng. Bên trong thành phần kết hợp NavigationRail, bạn sẽ thêm các phần tử NavigationRailItem
cho Trang chủ và Hồ sơ.
Hãy bắt đầu từ cách triển khai cơ bản cho Dải điều hướng.
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
@Composable
private fun SootheNavigationRail(modifier: Modifier = Modifier) {
NavigationRail(
) {
Column(
) {
NavigationRailItem(
icon = {
Icon(
imageVector = Icons.Default.Spa,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_home))
},
selected = true,
onClick = {}
)
NavigationRailItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_profile))
},
selected = false,
onClick = {}
)
}
}
}
Bạn nên điều chỉnh một số kiểu.
- Thêm khoảng đệm 8 dp ở đầu và cuối dải.
- Cập nhật màu nền của dải điều hướng: dùng màu nền của Giao diện Material để đặt tham số
containerColor
của dải điều hướng đó. Khi bạn đặt màu nền, màu của biểu tượng và văn bản sẽ tự động điều chỉnh theo màuonBackground
của giao diện. - Cột phải lấp đầy chiều cao tối đa.
- Đặt chế độ sắp xếp theo chiều dọc của cột vào chính giữa.
- Đặt chế độ căn chỉnh ngang của cột vào chính giữa theo chiều ngang.
- Thêm khoảng đệm 8 dp giữa 2 biểu tượng.
Giải pháp cuối cùng của bạn sẽ có dạng như sau:
import androidx.compose.foundation.layout.fillMaxHeight
@Composable
private fun SootheNavigationRail(modifier: Modifier = Modifier) {
NavigationRail(
modifier = modifier.padding(start = 8.dp, end = 8.dp),
containerColor = MaterialTheme.colorScheme.background,
) {
Column(
modifier = modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
NavigationRailItem(
icon = {
Icon(
imageVector = Icons.Default.Spa,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_home))
},
selected = true,
onClick = {}
)
Spacer(modifier = Modifier.height(8.dp))
NavigationRailItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_profile))
},
selected = false,
onClick = {}
)
}
}
}
Bây giờ, hãy thêm Dải điều hướng vào bố cục ngang.
Đối với phiên bản dọc của ứng dụng, bạn đã dùng một Scaffold. Tuy nhiên, đối với phiên bản ngang, bạn sẽ dùng một Hàng và đặt dải điều hướng và nội dung trên màn hình cạnh nhau.
@Composable
fun MySootheAppLandscape() {
MySootheTheme {
Row {
SootheNavigationRail()
HomeScreen()
}
}
}
Khi bạn dùng một Scaffold trong phiên bản dọc, việc này cũng giúp bạn đặt màu nội dung thành màu nền. Để đặt màu của Dải điều hướng, hãy gói Hàng đó trong một Surface và đặt thành màu nền.
@Composable
fun MySootheAppLandscape() {
MySootheTheme {
Surface(color = MaterialTheme.colorScheme.background) {
Row {
SootheNavigationRail()
HomeScreen()
}
}
}
}
14. Ứng dụng MySoothe – Kích thước cửa sổ
Bạn có Bản xem trước ở chế độ ngang trông khá ổn. Tuy nhiên, nếu bạn chạy ứng dụng trên một thiết bị hoặc trình mô phỏng rồi xoay sang cạnh bên, ứng dụng đó sẽ không hiện phiên bản ngang cho bạn. Đó là vì chúng ta cần cho ứng dụng biết thời điểm sẽ hiện cấu hình của ứng dụng. Để thực hiện việc này, hãy dùng hàm calculateWindowSizeClass()
để xem điện thoại đang ở cấu hình nào.
Có 3 chiều rộng lớp kích thước cửa sổ: Nhỏ gọn, Trung bình và Mở rộng. Khi ở chế độ dọc, ứng dụng có chiều rộng Nhỏ gọn, còn khi ở chế độ ngang, ứng dụng có chiều rộng Mở rộng. Trong phạm vi của lớp học lập trình này, bạn sẽ không làm việc với chiều rộng Trung bình.
Trong Thành phần kết hợp MySootheApp, hãy cập nhật để lấy trong WindowSizeClass của thiết bị. Nếu chiều rộng là nhỏ gọn, hãy truyền phiên bản dọc của ứng dụng. Nếu là chế độ ngang, hãy truyền phiên bản ngang của ứng dụng.
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@Composable
fun MySootheApp(windowSize: WindowSizeClass) {
when (windowSize.widthSizeClass) {
WindowWidthSizeClass.Compact -> {
MySootheAppPortrait()
}
WindowWidthSizeClass.Expanded -> {
MySootheAppLandscape()
}
}
}
Trong setContent()
, hãy tạo một val có tên là windowSizeClass và đặt thành calculateWindowSize()
rồi truyền nó vào MySootheApp().
Vì calculateWindowSize()
vẫn đang trong quá trình thử nghiệm nên bạn cần chọn sử dụng lớp ExperimentalMaterial3WindowSizeClassApi
.
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val windowSizeClass = calculateWindowSizeClass(this)
MySootheApp(windowSizeClass)
}
}
}
Bây giờ, hãy chạy ứng dụng trên trình mô phỏng hoặc thiết bị của bạn và quan sát cách màn hình thay đổi khi xoay.
15. 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 và tìm hiểu thêm về bố cục trong Compose. Thông qua việc triển khai thiết kế thực tế, bạn đã tìm hiểu về các đối tượng sửa đổi, căn chỉnh, sắp xếp, bố cục Lazy, API ô trống, cuộn, các thành phần Material và thiết kế bố cục cụ thể.
Hãy tham khảo các lớp học lập trình khác trên Lộ trình học tập về Compose. Đồng thời xem các mã mẫu.
Tài liệu
Để biết thêm thông tin và hướng dẫn về những chủ đề này, vui lòng xem các tài liệu sau: