Thông tin cơ bản về bố cục Compose

Jetpack Compose giúp việc thiết kế và xây dựng giao diện người dùng cho ứng dụng trở nên dễ dàng hơn nhiều. Compose chuyển đổi trạng thái thành các thành phần trên giao diện người dùng, thông qua:

  1. Cấu tạo của các thành phần
  2. Bố cục của các thành phần
  3. Bản vẽ các thành phần

Compose chuyển đổi trạng thái thành giao diện người dùng thông qua thành phần, bố cục, bản vẽ

Tài liệu này tập trung vào bố cục của các thành phần, giải thích một số khối xây dựng mà Compose đưa ra để giúp sắp đặt bố cục các thành phần trên giao diện người dùng.

Mục tiêu của các bố cục trong Compose

Việc triển khai hệ thống bố cục trong Jetpack Compose có hai mục tiêu chính:

Kiến thức cơ bản về hàm có khả năng kết hợp

Hàm có khả năng kết hợp là khối xây dựng cơ bản trong Compose. Hàm có khả năng kết hợp là một hàm chèn Unit mô tả một số phần trên giao diện người dùng. Hàm sử dụng một số dữ liệu đầu vào và tạo nội dung hiển thị trên màn hình. Để biết thêm thông tin về các thành phần kết hợp, hãy xem tài liệu Mô hình tư duy của Compose.

Một hàm có khả năng kết hợp có thể chuyển phát nhiều phần tử trên giao diện người dùng. Tuy nhiên, nếu bạn không đưa ra cách sắp xếp, thì Compose có thể sắp xếp các thành phần theo cách mà bạn không muốn. Ví dụ: mã này tạo ra hai thành phần văn bản:

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Nếu bạn không hướng dẫn cách sắp xếp các thành phần này, thì công cụ Compose sẽ xếp chồng các thành phần văn bản lên nhau, khiến cho chúng không thể đọc được:

Hai thành phần văn bản xếp chồng lên nhau khiến cho văn bản không thể đọc được

Compose cung cấp một tập hợp các bố cục sẵn sàng sử dụng để giúp bạn sắp xếp các thành phần trên giao diện người dùng và giúp bạn dễ dàng xác định các bố cục của riêng mình, ở mức độ chuyên biệt cao hơn.

Các thành phần của bố cục tiêu chuẩn

Trong nhiều trường hợp, bạn chỉ cần sử dụng Các thành phần của bố cục tiêu chuẩn trong Compose.

Hãy dùng Column để đặt các mục theo chiều dọc trên màn hình.

@Composable
fun ArtistCardColumn() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

Hai thành phần văn bản được sắp xếp theo bố cục cột, để văn bản có thể đọc được

Tương tự, hãy dùng Row để đặt các mục theo chiều ngang trên màn hình. Cả hai mã ColumnRow đều hỗ trợ định cấu hình căn chỉnh các thành phần có trong mã.

@Composable
fun ArtistCardRow(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

Hiển thị một bố cục phức tạp hơn, cùng một đồ hoạ nhỏ bên cạnh cột các thành phần văn bản

Sử dụng Box để xếp các thành phần chồng lên nhau. Box cũng hỗ trợ định cấu hình căn chỉnh cụ thể các thành phần có trong mã.

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Icon(Icons.Filled.Check, contentDescription = "Check mark")
    }
}

Hiển thị hai thành phần được xếp chồng lên nhau

Thông thường, các khối xây dựng này là tất cả những gì bạn cần. Bạn có thể viết hàm có khả năng kết hợp của riêng mình để kết hợp các bố cục này vào một bố cục chi tiết hơn phù hợp với ứng dụng của bạn.

So sánh ba hàm có khả năng kết hợp bố cục đơn giản: cột, hàng và hộp

Để đặt vị trí bố cục con trong Row, hãy đặt các đối số horizontalArrangementverticalAlignment. Đối với Column, hãy đặt các đối số verticalArrangementhorizontalAlignment:

@Composable
fun ArtistCardArrangement(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column { /*...*/ }
    }
}

Các mục được căn chỉnh về phía bên phải

Mô hình bố cục

Trong mô hình bố cục, cây giao diện người dùng được sắp xếp chỉ trong một luồng. Trước tiên, mỗi nút được yêu cầu tự đo lường, sau đó đo lường định kỳ bất cứ thành phần con cháu nào, chuyển các giới hạn kích thước xuống dọc theo cây cho các thành phần con cháu. Sau đó, cần định kích thước và đặt các nút lá, rồi các kích cỡ đã được xác định cùng các câu lệnh hướng dẫn vị trí được chuyển trở lại cây.

Tóm lại, cần đo lường các thành phần mẹ trước các thành phần con cháu, nhưng việc định kích thước và đặt các thành phần mẹ lại diễn ra sau các thành phần con cháu.

Hãy xem xét hàm SearchResult sau.

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

Hàm này tạo cây giao diện người dùng sau đây.

SearchResult
  Row
    Image
    Column
      Text
      Text

Trong ví dụ SearchResult, bố cục cây giao diện người dùng tuân theo thứ tự sau:

  1. Câu lệnh yêu cầu đo lường nút gốc Row.
  2. Nút gốc Row yêu cầu nút con cháu đầu tiên của nó, Image, đo lường.
  3. Image là một nút lá (nút không có nút con cháu), vì vậy nút này báo cáo kích thước và trả về các câu lệnh hướng dẫn vị trí.
  4. Nút gốc Row yêu cầu nút con cháu thứ hai của nó, Column, đo lường.
  5. Nút Column yêu cầu nút con cháu đầu tiên của nó, Text, đo lường.
  6. Nút Text đầu tiên là một nút lá. Nút lá chỉ báo cáo kích thước và trả về câu lệnh hướng dẫn vị trí.
  7. Nút Column yêu cầu nút con cháu Text thứ hai của nó đo lường.
  8. Nút Text thứ hai là một nút lá, vì vậy nút này báo cáo kích thước và trả về câu lệnh hướng dẫn vị trí.
  9. Hiện tại, nút Column đã đo lường xong, đã định kích thước và đặt các nút con cháu của nó, nút này có thể xác định kích thước và vị trí của riêng mình.
  10. Hiện tại, nút gốc Row đã đo lường xong, đã định kích thước và đặt các nút con cháu của nó, nút này có thể xác định kích thước và vị trí của riêng mình.

Thứ tự đo lường, định kích thước và đặt vị trí trong cây giao diện người dùng của phần Kết quả tìm kiếm

Hiệu suất

Compose đạt được hiệu suất cao bằng cách đo lường nút con cháu một lần duy nhất. Chế độ đo lường một luồng tốt cho hiệu suất, cho phép công cụ Compose xử lý sâu các cây giao diện người dùng một cách hiệu quả. Nếu một thành phần đo lường thành phần con cháu của nó hai lần và thành phần con cháu đó đo lường từng thành phần con cháu của nó hai lần và cứ tiếp tục như vậy, thì một lần thử duy nhất để tạo ra toàn bộ Giao diện người dùng sẽ phải thực hiện rất nhiều việc, khiến ứng dụng của bạn khó duy trì hoạt động.

Nếu bố cục của bạn cần nhiều lần đo lường vì một lý do nào đó, Compose có một hệ thống đặc biệt, phép đo lường hàm nội tại. Bạn có thể đọc thêm về tính năng này ở phần Phép đo lường hàm nội tại trong bố cục Compose.

Vì việc đo lường và vị trí là các giai đoạn phụ riêng biệt của luồng bố cục, nên các thay đổi chỉ ảnh hưởng đến vị trí của các mục mà không ảnh hưởng đến hoạt động đo lường, đều có thể được thực hiện riêng biệt.

Sử dụng đối tượng sửa đổi trong bố cục của bạn

Như đã đề cập ở phần các đối tượng sửa đổi trong Compose, bạn có thể sử dụng đối tượng sửa đổi để trang trí hoặc bổ sung các thành phần kết hợp của bạn. Các đối tượng sửa đổi là thành phần cần thiết để tuỳ chỉnh bố cục của bạn. Ví dụ: ở đây chúng tôi liên kết một số đối tượng sửa đổi để tuỳ chỉnh ArtistCard:

@Composable
fun ArtistCardModifiers(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) { /*...*/ }
    }
}

Bố cục phức tạp hơn, sử dụng các đối tượng sửa đổi để thay đổi cách sắp xếp đồ hoạ và khu vực phản hồi cho người dùng nhập thông tin vào

Trong mã trên, hãy chú ý đến các đối tượng sửa đổi có chức năng khác nhau được sử dụng cùng nhau.

  • clickable tạo một phản ứng kết hợp với thông tin do người dùng nhập và hiển thị một hiệu ứng gợn sóng.
  • padding đặt khoảng trống quanh một thành phần.
  • fillMaxWidth làm cho thành phần kết hợp lấp đầy chiều rộng tối đa mà thành phần mẹ đã cấp cho nó.
  • size() xác định chiều rộng và chiều cao ưu tiên của một thành phần.

Bố cục có thể cuộn

Hãy tìm hiểu thêm về bố cục có thể cuộn trong tài liệu về hành động của Compose.

Để xem các danh sách và danh sách tải lười, hãy xem tài liệu về hành động của Compose.

Bố cục thích ứng

Bạn nên thiết kế bố cục với các hướng màn hình khác nhau và kích thước kiểu dáng. Compose cung cấp một số cơ chế để hỗ trợ điều chỉnh các bố cục có thể kết hợp của bạn với nhiều cấu hình màn hình khác nhau.

Giới hạn

Để biết các giới hạn từ thành phần mẹ và thiết kế bố cục tương ứng, bạn có thể sử dụng BoxWithConstraints. Bạn có thể tìm thấy các giới hạn về đo lường trong phạm vi của nội dung lambda. Bạn có thể sử dụng các giới hạn đo lường này để phát triển các bố cục khác nhau cho các cấu hình màn hình khác nhau:

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

Bố cục theo khe

Compose cung cấp nhiều loại thành phần kết hợp dựa trên Material Design cùng với phần phụ thuộc androidx.compose.material:material (được bao gồm khi tạo một dự án Compose trong Android Studio) để giúp bạn dễ dàng xây dựng Giao diện người dùng. Các thành phần như Drawer, FloatingActionButtonTopAppBar đều được cung cấp.

Các thành phần cụ thể sử dụng nhiều API khe, một mẫu mà Compose đưa ra để sử dụng một lớp tuỳ chỉnh ở phía cùng các thành phần kết hợp. Phương pháp tiếp cận này giúp các thành phần linh hoạt hơn, vì chúng chấp nhận một thành phần con cháu có thể tự định cấu hình thay vì phải biểu thị mọi thông số cấu hình của thành phần con cháu. Các khe sẽ để trống trong giao diện người dùng để nhà phát triển lấp đầy theo ý muốn. Ví dụ: đây là các khe mà bạn có thể tuỳ chỉnh trong TopAppBar:

Sơ đồ biểu thị các khe có sẵn trên thanh ứng dụng của khối xây dựng Material Components

Các thành phần kết hợp thường lấy một hàm lambda kết hợp content (content: @Composable () -> Unit). API khe biểu thị nhiều thông số content cho các mục đích sử dụng cụ thể. Ví dụ: TopAppBar cho phép bạn cung cấp nội dung cho title, navigationIconactions.

Ví dụ: Scaffold cho phép bạn triển khai giao diện người dùng với cấu trúc bố cục Material Design cơ bản. Scaffoldcung cấp khe cho các thành phần Vật liệu cấp cao nhất phổ biến nhất, chẳng hạn như TopAppBar, BottomAppBar, FloatingActionButtonDrawer. Bằng cách sử dụng Scaffold, việc đảm bảo các thành phần này được đặt đúng vị trí và hoạt động cùng nhau một cách chính xác trở nên vô cùng đơn giản.

Ứng dụng mẫu JetNews, sử dụng Scafford để định vị nhiều thành phần

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}