Phép đo nội tại trong bố cục Compose

Compose có một quy tắc là bạn chỉ nên đo lường thành phần con một lần; việc đo lường thành phần con hai lần sẽ cho ra một ngoại lệ trong thời gian chạy. Tuy nhiên, có một số trường hợp bạn cần thêm thông tin về thành phần con trước khi đo lường.

Hàm nội tại (intrinsics) cho phép bạn truy vấn thành phần con trước khi thực sự đo lường.

Đối với thành phần kết hợp, bạn có thể yêu cầu IntrinsicSize.Min hoặc IntrinsicSize.Max:

  • Modifier.width(IntrinsicSize.Min) – Chiều rộng tối thiểu cần thiết để hiển thị nội dung của bạn một cách phù hợp là bao nhiêu?
  • Modifier.width(IntrinsicSize.Max) – Bạn cần chiều rộng tối đa là bao nhiêu để hiển thị nội dung một cách phù hợp?
  • Modifier.height(IntrinsicSize.Min) – Chiều cao tối thiểu cần thiết để hiển thị nội dung một cách phù hợp là bao nhiêu?
  • Modifier.height(IntrinsicSize.Max) – Chiều cao tối đa bạn cần để hiển thị nội dung một cách phù hợp là bao nhiêu?

Ví dụ: nếu bạn yêu cầu minIntrinsicHeight của một Text có các ràng buộc width vô hạn trong một bố cục tuỳ chỉnh, thì thao tác này sẽ trả về height của Text với văn bản được vẽ trong một dòng duy nhất.

Hàm nội tại trong thực tế

Bạn có thể tạo một thành phần kết hợp hiển thị hai đoạn văn bản trên màn hình được phân tách bằng một đường phân cách:

Hai thành phần văn bản cạnh nhau, ở giữa có một đường phân cách dọc

Để làm việc này, hãy dùng một Row có 2 thành phần kết hợp Text lấp đầy không gian có sẵn và một Divider ở giữa. Divider phải cao bằng Text cao nhất và phải hẹp (width = 1.dp).

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        VerticalDivider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

Divider mở rộng ra toàn màn hình, đây không phải là hành vi mong muốn:

Hai thành phần văn bản cạnh nhau, ở giữa có một đường phân cách, nhưng đường phân cách kéo giãn xuống phần dưới văn bản

Điều này xảy ra vì Row đo lường từng thành phần con riêng lẻ và không thể sử dụng chiều cao của Text để ràng buộc Divider.

Để Divider lấp đầy không gian hiện có với một chiều cao nhất định, hãy sử dụng đối tượng sửa đổi height(IntrinsicSize.Min).

height(IntrinsicSize.Min) cho phép thành phần con cao hơn chiều cao nội tại tối thiểu. Vì đối tượng sửa đổi này có tính đệ quy, nên nó sẽ truy vấn minIntrinsicHeight của Row và các thành phần con của thành phần này.

Khi áp dụng đối tượng sửa đổi này vào mã, mã sẽ hoạt động như dự kiến:

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier.height(IntrinsicSize.Min)) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        VerticalDivider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

// @Preview
@Composable
fun TwoTextsPreview() {
    MaterialTheme {
        Surface {
            TwoTexts(text1 = "Hi", text2 = "there")
        }
    }
}

Bản xem trước:

Hai thành phần văn bản cạnh nhau, ở giữa có một đường phân cách dọc

Chiều cao của Row được xác định như sau:

  • minIntrinsicHeight của thành phần kết hợp RowminIntrinsicHeight tối đa của thành phần con.
  • minIntrinsicHeight của phần tử Divider là 0 vì không chiếm dung lượng nếu không có điều kiện ràng buộc nào được đưa ra.
  • Text minIntrinsicHeight là của văn bản cho một width cụ thể.
  • Do đó, điều kiện ràng buộc height của phần tử Row sẽ trở thành minIntrinsicHeight tối đa của Text.
  • Sau đó, Divider sẽ mở rộng height đến giới hạn height do Row đưa ra.

Hàm nội tại trong bố cục tuỳ chỉnh

Khi tạo một phương thức sửa đổi Layout hoặc layout tuỳ chỉnh, hàm đo lường nội tại sẽ được tự động tính toán dựa trên giá trị gần đúng. Do đó, cách tính có thể không chính xác cho tất cả bố cục. Các API này có các tuỳ chọn để ghi đè những giá trị mặc định như vậy.

Để chỉ định các phép đo hàm nội tại của Layout tuỳ chỉnh, hãy ghi đè minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidthmaxIntrinsicHeight của giao diện MeasurePolicy khi tạo.

@Composable
fun MyCustomComposable(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        content = content,
        modifier = modifier,
        measurePolicy = object : MeasurePolicy {
            override fun MeasureScope.measure(
                measurables: List<Measurable>,
                constraints: Constraints
            ): MeasureResult {
                // Measure and layout here
                // ...
            }

            override fun IntrinsicMeasureScope.minIntrinsicWidth(
                measurables: List<IntrinsicMeasurable>,
                height: Int
            ): Int {
                // Logic here
                // ...
            }

            // Other intrinsics related methods have a default value,
            // you can override only the methods that you need.
        }
    )
}

Khi tạo phương thức sửa đổi layout tuỳ chỉnh, hãy ghi đè các phương thức có liên quan trong giao diện LayoutModifier.

fun Modifier.myCustomModifier(/* ... */) = this then object : LayoutModifier {

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        // Measure and layout here
        // ...
    }

    override fun IntrinsicMeasureScope.minIntrinsicWidth(
        measurable: IntrinsicMeasurable,
        height: Int
    ): Int {
        // Logic here
        // ...
    }

    // Other intrinsics related methods have a default value,
    // you can override only the methods that you need.
}