Giới thiệu về WindowInsetsRulers

WindowInsets là API tiêu chuẩn trong Jetpack Compose để xử lý các vùng màn hình bị giao diện người dùng hệ thống che khuất một phần hoặc toàn bộ. Các vùng này bao gồm thanh trạng thái, thanh điều hướng và bàn phím ảo. Ngoài ra, bạn có thể truyền WindowInsetsRulers được xác định trước như SafeDrawing đến Modifier.fitInside hoặc Modifier.fitOutside để căn chỉnh nội dung với thanh hệ thống và vết cắt trên màn hình hoặc tạo WindowInsetsRulers tuỳ chỉnh.

Ưu điểm của WindowInsetsRulers

  • Tránh sự phức tạp khi sử dụng: Nó hoạt động trong giai đoạn đặt của bố cục. Điều này có nghĩa là nó hoàn toàn bỏ qua chuỗi sử dụng phần lồng ghép và luôn có thể cung cấp vị trí tuyệt đối, chính xác của các thanh hệ thống và vết cắt trên màn hình, bất kể bố cục mẹ đã thực hiện những gì. Việc sử dụng các phương thức Modifier.fitInside hoặc Modifier.fitOutside sẽ giúp ích trong việc khắc phục các vấn đề khi thành phần kết hợp tổ tiên tiêu thụ không chính xác các phần lồng ghép.
  • Dễ dàng tránh thanh hệ thống: Giúp nội dung ứng dụng của bạn tránh thanh hệ thống và vết cắt trên màn hình, đồng thời có thể đơn giản hơn so với việc sử dụng trực tiếp WindowInsets.
  • Có thể tuỳ chỉnh cao: Nhà phát triển có thể căn chỉnh nội dung theo các thước đo tuỳ chỉnh và kiểm soát chính xác bố cục bằng bố cục tuỳ chỉnh.

Nhược điểm của WindowInsetsRulers

  • Không thể dùng cho hoạt động đo lường: Vì hoạt động này diễn ra trong giai đoạn đặt, nên thông tin vị trí mà hoạt động này cung cấp không có sẵn trong giai đoạn đo lường trước đó.

Điều chỉnh nội dung của bạn theo các phương thức Modifier

Modifier.fitInside cho phép các ứng dụng căn chỉnh nội dung với thanh hệ thống và vết cắt trên màn hình. Bạn có thể dùng phương thức này thay cho WindowInsets. Modifier.fitOutside thường là nghịch đảo của Modifier.fitInside.

Ví dụ: để xác minh rằng nội dung ứng dụng tránh thanh hệ thống và vết cắt trên màn hình, bạn có thể sử dụng fitInside(WindowInsetsRulers.safeDrawing.current).

@Composable
fun FitInsideDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            // Or DisplayCutout, Ime, NavigationBars, StatusBar, etc...
            .fitInside(WindowInsetsRulers.SafeDrawing.current)
    )
}

Bảng sau đây cho biết nội dung ứng dụng của bạn sẽ trông như thế nào với các thước đo được xác định trước bằng Modifier.fitInside hoặc Modifier.fitOutside.

Loại thước kẻ được xác định trước

Modifier.fitInside

Modifier.fitOutside

DisplayCutout

Ime

Không áp dụng

NavigationBars

SafeDrawing

Không áp dụng (thay vào đó, hãy dùng StatusBar, CaptionBar, NavigationBar)

StatusBar

Để sử dụng Modifier.fitInsideModifier.fitOutside, bạn phải hạn chế các thành phần kết hợp. Điều này có nghĩa là bạn phải xác định các đối tượng sửa đổi như Modifier.size hoặc Modifier.fillMaxSize.

Một số quy tắc như Modifier.fitOutside trên SafeDrawingSystemBars trả về nhiều quy tắc. Trong trường hợp này, Android sẽ đặt Thành phần kết hợp bằng một thước đo từ trái, trên cùng, phải, dưới cùng.

Tránh IME bằng Modifier.fitInside

Để xử lý các phần tử dưới cùng bằng IME có Modifier.fitInside, hãy truyền vào một RectRuler lấy giá trị trong cùng của NavigationBarIme.

@Composable
fun FitInsideWithImeDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.NavigationBars.current,
                    WindowInsetsRulers.Ime.current
                )
            )
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier.align(Alignment.BottomStart).fillMaxWidth()
        )
    }
}

Tránh thanh trạng thái và thanh chú thích bằng Modifier.fitInside

Tương tự, để xác minh các phần tử trên cùng, hãy tránh thanh trạng thái và thanh chú thích cùng với Modifier.fitInsider, truyền một RectRuler lấy giá trị trong cùng của StatusBarsCaptionBar.

@Composable
fun FitInsideWithStatusAndCaptionBarDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.StatusBars.current,
                    WindowInsetsRulers.CaptionBar.current
                )
            )
    )
}

Tạo WindowInsetsRulers tuỳ chỉnh

Bạn có thể căn chỉnh nội dung theo thước đo tuỳ chỉnh. Ví dụ: hãy xem xét trường hợp sử dụng trong đó một thành phần kết hợp mẹ xử lý không đúng phần lồng ghép, gây ra các vấn đề về khoảng đệm ở một thành phần con hạ lưu. Mặc dù có thể giải quyết vấn đề này bằng những cách khác, bao gồm cả việc sử dụng Modifier.fitInside, bạn cũng có thể tạo một thước đo tuỳ chỉnh để căn chỉnh chính xác thành phần kết hợp con mà không cần phải khắc phục vấn đề ở thành phần kết hợp mẹ như trong ví dụ và video sau:

@Composable
fun WindowInsetsRulersDemo(modifier: Modifier) {
    Box(
        contentAlignment = BottomCenter,
        modifier = modifier
            .fillMaxSize()
            // The mistake that causes issues downstream, as .padding doesn't consume insets.
            // While it's correct to instead use .windowInsetsPadding(WindowInsets.navigationBars),
            // assume it's difficult to identify this issue to see how WindowInsetsRulers can help.
            .padding(WindowInsets.navigationBars.asPaddingValues())
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier
                // Use alignToSafeDrawing() instead of .imePadding() to precisely place this child
                // Composable without having to fix the parent upstream.
                .alignToSafeDrawing()

            // .imePadding()
            // .fillMaxWidth()
        )
    }
}

fun Modifier.alignToSafeDrawing(): Modifier {
    return layout { measurable, constraints ->
        if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) {
            val placeable = measurable.measure(constraints)
            val width = placeable.width
            val height = placeable.height
            layout(width, height) {
                val bottom = WindowInsetsRulers.SafeDrawing.current.bottom
                    .current(0f).roundToInt() - height
                val right = WindowInsetsRulers.SafeDrawing.current.right
                    .current(0f).roundToInt()
                val left = WindowInsetsRulers.SafeDrawing.current.left
                    .current(0f).roundToInt()
                measurable.measure(Constraints.fixed(right - left, height))
                    .place(left, bottom)
            }
        } else {
            val placeable = measurable.measure(constraints)
            layout(placeable.width, placeable.height) {
                placeable.place(0, 0)
            }
        }
    }
}

Video sau đây cho thấy ví dụ về việc sử dụng IME chèn có vấn đề do một thành phần mẹ ở phía trên trong hình ảnh bên trái và sử dụng các thước đo tuỳ chỉnh để khắc phục vấn đề ở bên phải. Khoảng đệm bổ sung xuất hiện bên dưới TextFieldComposable vì khoảng đệm của thanh điều hướng không được thành phần mẹ sử dụng. Phần tử con được đặt ở đúng vị trí trong hình ảnh bên phải bằng cách sử dụng một thước đo tuỳ chỉnh như trong mẫu mã trước đó.

Xác minh rằng cha mẹ bị hạn chế

Để sử dụng WindowInsetsRulers một cách an toàn, hãy đảm bảo rằng thành phần mẹ cung cấp các ràng buộc hợp lệ. Phần tử mẹ phải có kích thước xác định và không thể phụ thuộc vào kích thước của phần tử con sử dụng WindowInsetsRulers. Sử dụng fillMaxSize hoặc các đối tượng sửa đổi kích thước khác trên các thành phần kết hợp mẹ.

Tương tự, việc đặt một thành phần kết hợp sử dụng WindowInsetsRulers bên trong một vùng chứa có thể cuộn như verticalScroll có thể gây ra hành vi không mong muốn vì vùng chứa có thể cuộn cung cấp các ràng buộc chiều cao không giới hạn, không tương thích với logic của thước đo.