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 intrinsicWidth hoặc intrinsicHeight:

  • (min|max)IntrinsicWidth: Với chiều rộng này, giá trị tối thiểu/tối đa là bao nhiêu bạn có thể vẽ nội dung hợp lý không?
  • (min|max)IntrinsicHeight: Với chiều cao này, giá trị tối thiểu/tối đa là bao nhiêu bạn có thể tô màu nội dung cho phù hợp không?

Ví dụ: nếu bạn yêu cầu minIntrinsicHeight của Text có vô hạn height, thì hàm này sẽ trả về height của Text như thể văn bản được vẽ trong dòng đơn.

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

Hãy tưởng tượng chúng ta muốn tạo một thành phần kết hợp có thể 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 như sau:

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

Chúng ta có thể làm việc này bằng cách nào? Chúng ta có thể có một Row với hai Text bên trong mở rộng nhiều nhất có thể và một Divider ở giữa. Chúng ta muốn Divider giống như người cao nhất Text và người gầy (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
        )
        Divider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

Nếu xem trước, chúng ta sẽ thấy Divider mở rộng ra toàn bộ màn hình và đó không phải là điều chúng tôi 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. Chúng ta muốn Divider lấp đầy không gian hiện có với một chiều cao nhất định. Do đó, chúng ta có thể sử dụng phương thức 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ì có tính đệ quy, lượt truy vấn này sẽ truy vấn Row và cũng như minIntrinsicHeight của thành phần con.

Khi áp dụng mã đó, mã sẽ 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
        )
        Divider(
            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

minIntrinsicHeight của thành phần kết hợp Row sẽ là minIntrinsicHeight 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 sẽ bằng của văn bản đã cung cấp một width cụ thể. Do đó, điều kiện ràng buộc height của phần tử Row sẽ là minIntrinsicHeight tối đa của Text. Sau đó, Divider sẽ mở rộng height tới 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.
}