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 đúng cách là bao nhiêu?
  • Modifier.width(IntrinsicSize.Max) – Chiều rộng tối đa cần thiết để hiển thị nội dung đúng cách là bao nhiêu?
  • Modifier.height(IntrinsicSize.Min) – Chiều cao tối thiểu cần thiết để hiển thị nội dung đúng cách là bao nhiêu?
  • Modifier.height(IntrinsicSize.Max) - Chiều cao tối đa cần thiết để hiển thị nội dung đúng cách là bao nhiêu?

Ví dụ: nếu bạn yêu cầu minIntrinsicHeight của một Text có các điều kiện 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ị 2 đ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

Để thực hiện việc này, hãy sử dụng một Row có 2 thành phần kết hợp Text lấp đầy không gian hiện có 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 đối tượng này sẽ truy vấn minIntrinsicHeight của Row và thành phần con.

Việc áp dụng đối tượng sửa đổi này vào mã sẽ giúp mã hoạt động như mong đợi:

@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ì phần tử này không chiếm không gian nếu không có điều kiện ràng buộc nào được đưa ra.
  • minIntrinsicHeight của Text 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 trở thành minIntrinsicHeight tối đa của Text.
  • Sau đó, Divider mở rộng height đến giới hạn height do Row cung cấp.

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, hãy ghi đè minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, và maxIntrinsicHeight 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 đối tượng 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.
}