ConstraintLayout trong Compose

ConstraintLayout là một bố cục cho phép bạn đặt các thành phần kết hợp tương ứng với các thành phần kết hợp khác trên màn hình. Đây là giải pháp thay thế cho việc sử dụng nhiều phần tử bố cục lồng nhau Row, Column, Boxcác phần tử bố cục tuỳ chỉnh khác. ConstraintLayout rất hữu ích khi triển khai bố cục lớn hơn với các yêu cầu căn chỉnh phức tạp hơn.

Hãy cân nhắc sử dụng ConstraintLayout trong các trường hợp sau:

  • Để tránh lồng nhiều ColumnRow cho các phần tử định vị trên màn hình nhằm cải thiện khả năng đọc mã.
  • Định vị các thành phần kết hợp tương ứng với các thành phần kết hợp khác hoặc để định vị các thành phần kết hợp dựa trên nguyên tắc, rào cản hoặc chuỗi.

Trong hệ thống Khung hiển thị, bạn nên sử dụng ConstraintLayout để tạo các bố cục lớn và phức tạp, vì hệ phân cấp khung hiển thị phẳng hiệu quả hơn khung hiển thị lồng nhau. Tuy nhiên, điều này không phải là vấn đề đáng lo ngại trong Compose vì tính năng này có thể xử lý hiệu quả các hệ phân cấp bố cục sâu.

Làm quen với ConstraintLayout

Để sử dụng ConstraintLayout trong Compose, bạn cần thêm phần phụ thuộc này vào build.gradle (ngoài chế độ thiết lập Compose):

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

ConstraintLayout trong Compose hoạt động theo cách bên dưới bằng cách sử dụng DSL:

  • Tạo tệp đối chiếu cho từng thành phần có thể kết hợp trong ConstraintLayout bằng createRefs() hoặc createRefFor()
  • Các giới hạn được cung cấp bằng cách sử dụng công cụ sửa đổi constrainAs(), lấy tham chiếu làm tham số và cho phép bạn chỉ định các giới hạn của hệ thống trong phần thân lambda.
  • Bạn có thể chỉ định các quy tắc ràng buộc bằng cách sử dụng linkTo() hoặc các phương pháp hữu ích khác.
  • parent là một tệp đối chiếu hiện có thể dùng để chỉ định các giới hạn cho chính thành phần kết hợp ConstraintLayout.

Dưới đây là ví dụ về một thành phần kết hợp sử dụng ConstraintLayout:

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout {
        // Create references for the composables to constrain
        val (button, text) = createRefs()

        Button(
            onClick = { /* Do something */ },
            // Assign reference "button" to the Button composable
            // and constrain it to the top of the ConstraintLayout
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("Button")
        }

        // Assign reference "text" to the Text composable
        // and constrain it to the bottom of the Button composable
        Text("Text", Modifier.constrainAs(text) {
            top.linkTo(button.bottom, margin = 16.dp)
        })
    }
}

Mã này ràng buộc phần đầu của Button thành phần tử cha có lề 16.dp và một phần Text ở dưới cùng Button cũng với lề 16.dp.

Hiển thị một nút và một thành phần văn bản được sắp xếp trong ConstraintLayout

API đã phân tách

Trong ví dụ ConstraintLayout, các giới hạn được chỉ định cùng dòng, kèm theo đối tượng sửa đổi trong các tệp đối chiếu mà các quy tắc đó được áp dụng. Tuy nhiên, có thể có những trường hợp bạn nên tách riêng các giới hạn khỏi bố cục áp dụng. Ví dụ: bạn có thể thay đổi các giới hạn dựa trên cấu hình màn hình hoặc tạo ảnh động giữa 2 tập hợp các giới hạn.

Đối với các trường hợp như vậy, bạn có thể sử dụng ConstraintLayout theo cách khác.

  1. Truyền vào ConstraintSet dưới dạng tham số cho ConstraintLayout.
  2. Chỉ định các tệp đối chiếu được tạo trong ConstraintSet cho các tệp sáng tạo bằng cách sử dụng đối tượng sửa đổi layoutId.
@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // Portrait constraints
        } else {
            decoupledConstraints(margin = 32.dp) // Landscape constraints
        }

        ConstraintLayout(constraints) {
            Button(
                onClick = { /* Do something */ },
                modifier = Modifier.layoutId("button")
            ) {
                Text("Button")
            }

            Text("Text", Modifier.layoutId("text"))
        }
    }
}

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        constrain(button) {
            top.linkTo(parent.top, margin = margin)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin)
        }
    }
}

Sau đó, khi cần thay đổi các giới hạn, bạn có thể truyền một ConstraintSet khác.

Các khái niệm về ConstraintLayout

ConstraintLayout chứa các khái niệm như nguyên tắc, rào cản và chuỗi có thể giúp xác định vị trí thành phần bên trong Thành phần kết hợp.

Nguyên tắc

Nguyên tắc là các trình trợ giúp trực quan nhỏ để thiết kế bố cục. Các thành phần kết hợp có thể bị hạn chế theo nguyên tắc. Nguyên tắc này hữu ích khi cần xác định vị trí các phần tử ở một dp hoặc percentage nhất định bên trong thành phần kết hợp cha.

Có 2 loại nguyên tắc khác nhau là dọc và ngang. 2 chiều ngang là topbottom, và 2 chiều dọc là startend.

 // Create guideline from the start of the parent at 10% the width of the Composable
val startGuideline = createGuidelineFromStart(0.1f)
// Create guideline from the end of the parent at 10% the width of the Composable
val endGuideline = createGuidelineFromEnd(0.1f)
//  Create guideline from 16 dp from the top of the parent
val topGuideline = createGuidelineFromTop(16.dp)
//  Create guideline from 16 dp from the bottom of the parent
val bottomGuideline = createGuidelineFromBottom(16.dp)

Để tạo một nguyên tắc, hãy dùng createGuidelineFrom* với loại nguyên tắc bắt buộc. Thao tác này sẽ tạo một tệp đối chiếu có thể dùng trong khối Modifier.constrainAs().

Rào cản

Các rào cản tham chiếu nhiều thành phần kết hợp để tạo nguyên tắc ảo dựa trên tiện ích cực đoan nhất ở phía được chỉ định.

Để tạo rào cản, hãy sử dụng createTopBarrier() (hoặc createBottomBarrier(), createEndBarrier(), createStartBarrier()) và cung cấp các tệp đối chiếu sẽ tạo nên rào cản.

val topBarrier = createTopBarrier(button, text)

Sau đó, bạn có thể sử dụng thanh chắn trong khối Modifier.constrainAs().

Chuỗi

Chuỗi cung cấp hành vi giống nhóm trong một trục (theo chiều ngang hoặc chiều dọc). Trục còn lại có thể bị hạn chế một cách độc lập.

Để tạo chuỗi, hãy sử dụng createVerticalChain hoặc createHorizontalChain:

val verticalChain = createVerticalChain(button, text, chainStyle = ChainStyle.Spread)
val horizontalChain = createHorizontalChain(button, text)

Sau đó, bạn có thể sử dụng chuỗi này trong khối Modifier.constrainAs().

Bạn có thể định cấu hình chuỗi bằng các ChainStyles khác nhau để quyết định cách xử lý không gian xung quanh một thành phần kết hợp, chẳng hạn như:

  • ChainStyle.Spread: Không gian được phân bổ đồng đều trên tất cả các thành phần kết hợp, bao gồm cả không gian trống trước thành phần kết hợp đầu tiên và sau thành phần kết hợp cuối cùng.
  • ChainStyle.SpreadInside: Không gian được phân bổ đồng đều trên tất cả các thành phần kết hợp mà không có không gian trống trước thành phần kết hợp đầu tiên hoặc sau thành phần kết hợp cuối cùng.
  • ChainStyle.Packed: Không gian được phân bổ trước thành phần kết hợp đầu tiên và sau thành phần kết hợp cuối cùng, các thành phần kết hợp được gói với nhau mà không có khoảng trống ở giữa.

Tìm hiểu thêm

Tìm hiểu thêm về ConstraintLayout trong Compose qua các API đang hoạt động ở phần Mẫu Compose được sử dụng ConstraintLayout.