Thiết lập phần lồng ghép cửa sổ

Để cho phép ứng dụng của bạn toàn quyền kiểm soát vị trí vẽ nội dung, hãy làm theo các bước thiết lập sau. Nếu không thực hiện các bước này, ứng dụng của bạn có thể vẽ màu đen hoặc màu đơn sắc phía sau giao diện người dùng hệ thống hoặc không tạo hiệu ứng động đồng bộ với bàn phím phần mềm.

  1. Nhắm đến Android 15 (API cấp 35) trở lên để thực thi chế độ hiển thị tràn viền trên Android 15 trở lên. Ứng dụng của bạn hiển thị phía sau giao diện người dùng hệ thống. Bạn có thể điều chỉnh giao diện người dùng của ứng dụng bằng cách xử lý phần lồng ghép.
  2. Bạn có thể gọi enableEdgeToEdge() trong Activity.onCreate(). Thao tác này cho phép ứng dụng của bạn hiển thị tràn viền trên các phiên bản Android trước.
  3. Đặt android:windowSoftInputMode="adjustResize" trong mục AndroidManifest.xml của Hoạt động. Chế độ cài đặt này cho phép ứng dụng của bạn nhận kích thước của IME phần mềm dưới dạng phần lồng ghép, giúp bạn áp dụng bố cục và khoảng đệm thích hợp khi IME xuất hiện và biến mất trong ứng dụng.

    <!-- In your AndroidManifest.xml file: -->
    <activity
      android:name=".ui.MainActivity"
      android:label="@string/app_name"
      android:windowSoftInputMode="adjustResize"
      android:theme="@style/Theme.MyApplication"
      android:exported="true">
    

Sử dụng API Compose

Sau khi Hoạt động của bạn kiểm soát việc xử lý tất cả các phần lồng ghép, bạn có thể sử dụng các API Compose để đảm bảo rằng nội dung không bị che khuất và các phần tử có thể tương tác không trùng lặp với giao diện người dùng hệ thống. Các API này cũng đồng bộ hoá bố cục của ứng dụng với các thay đổi về phần lồng ghép.

Ví dụ: đây là phương thức cơ bản nhất để áp dụng phần lồng ghép cho nội dung của toàn bộ ứng dụng:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    enableEdgeToEdge()

    setContent {
        Box(Modifier.safeDrawingPadding()) {
            // the rest of the app
        }
    }
}

Đoạn mã này áp dụng phần lồng ghép cửa sổ safeDrawing làm khoảng đệm xung quanh toàn bộ nội dung của ứng dụng. Mặc dù điều này đảm bảo rằng các phần tử có thể tương tác không trùng lặp với giao diện người dùng hệ thống, nhưng điều này cũng có nghĩa là không có phần nào của ứng dụng sẽ vẽ phía sau giao diện người dùng hệ thống để đạt được hiệu ứng tràn viền. Để tận dụng tối đa toàn bộ cửa sổ, bạn cần tinh chỉnh vị trí áp dụng phần lồng ghép trên cơ sở từng màn hình hoặc từng thành phần.

Tất cả các loại phần lồng ghép này đều được tạo ảnh động tự động bằng ảnh động IME được chuyển ngược về API 21. Do đó, tất cả bố cục của bạn sử dụng các phần lồng ghép này cũng sẽ tự động được tạo hiệu ứng khi các giá trị phần lồng ghép thay đổi.

Có 2 cách chính để sử dụng các loại phần lồng ghép này nhằm điều chỉnh bố cục Thành phần kết hợp: đối tượng sửa đổi khoảng đệm và đối tượng sửa đổi kích thước phần lồng ghép.

Đối tượng sửa đổi khoảng đệm

Modifier.windowInsetsPadding(windowInsets: WindowInsets) áp dụng phần lồng ghép cửa sổ đã cho làm khoảng đệm, hoạt động giống như Modifier.padding. Ví dụ: Modifier.windowInsetsPadding(WindowInsets.safeDrawing) áp dụng phần lồng ghép vẽ an toàn làm khoảng đệm ở cả 4 phía.

Ngoài ra, còn có một số phương thức tiện ích tích hợp sẵn cho các loại phần lồng ghép phổ biến nhất. Modifier.safeDrawingPadding() là một phương thức như vậy, tương đương với Modifier.windowInsetsPadding(WindowInsets.safeDrawing). Có các đối tượng sửa đổi tương tự cho các loại phần lồng ghép khác.

Đối tượng sửa đổi kích thước phần lồng ghép

Các đối tượng sửa đổi sau đây áp dụng một lượng phần lồng ghép cửa sổ bằng cách đặt kích thước của thành phần thành kích thước của phần lồng ghép:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Áp dụng phía bắt đầu của windowInsets làm chiều rộng (chẳng hạn như Modifier.width)

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Áp dụng phía cuối của windowInsets làm chiều rộng (chẳng hạn như Modifier.width)

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

Áp dụng cạnh trên cùng của windowInsets làm chiều cao (chẳng hạn như Modifier.height)

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Áp dụng phía dưới cùng của windowInsets làm chiều cao (chẳng hạn như Modifier.height)

Các đối tượng sửa đổi này đặc biệt hữu ích khi định cỡ một Spacer chiếm không gian của phần lồng ghép:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Mức tiêu thụ phần lồng ghép

Các đối tượng sửa đổi khoảng đệm lồng ghép (windowInsetsPadding và các đối tượng hỗ trợ như safeDrawingPadding) sẽ tự động sử dụng phần lồng ghép được áp dụng làm khoảng đệm. Trong khi đi sâu hơn vào cây thành phần, các đối tượng sửa đổi khoảng đệm lồng ghép và đối tượng sửa đổi kích thước phần lồng ghép biết rằng một phần của phần lồng ghép đã được các đối tượng sửa đổi khoảng đệm phần lồng ghép bên ngoài sử dụng và tránh sử dụng cùng một phần của phần lồng ghép nhiều lần, điều này sẽ dẫn đến quá nhiều khoảng trống.

Các đối tượng sửa đổi kích thước phần lồng ghép cũng tránh sử dụng cùng một phần của phần lồng ghép nhiều lần nếu phần lồng ghép đã được sử dụng. Tuy nhiên, vì các cửa sổ này thay đổi kích thước trực tiếp nên chúng không sử dụng phần lồng ghép.

Do đó, các đối tượng sửa đổi khoảng đệm lồng nhau sẽ tự động thay đổi lượng khoảng đệm được áp dụng cho mỗi thành phần kết hợp.

Hãy xem xét ví dụ LazyColumn tương tự như trước, LazyColumn đang được đổi kích thước bằng đối tượng sửa đổi imePadding. Bên trong LazyColumn, mục cuối cùng được định cỡ theo chiều cao của cuối thanh hệ thống:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Khi IME bị đóng, đối tượng sửa đổi imePadding() sẽ không áp dụng khoảng đệm vì IME không có chiều cao. Vì đối tượng sửa đổi imePadding() không áp dụng khoảng đệm, nên không có phần lồng ghép nào được sử dụng và chiều cao của Spacer sẽ là kích thước của phía dưới cùng của các thanh hệ thống.

Khi IME mở, phần lồng ghép IME sẽ tạo ảnh động để khớp với kích thước của IME và công cụ sửa đổi imePadding() bắt đầu áp dụng khoảng đệm dưới cùng để đổi kích thước LazyColumn khi IME mở. Khi đối tượng sửa đổi imePadding() bắt đầu áp dụng khoảng đệm dưới cùng, đối tượng này cũng bắt đầu sử dụng lượng phần lồng ghép đó. Do đó, chiều cao của Spacer bắt đầu giảm, vì một phần khoảng cách cho các thanh hệ thống đã được áp dụng bằng đối tượng sửa đổi imePadding(). Sau khi đối tượng sửa đổi imePadding() áp dụng một lượng khoảng đệm dưới lớn hơn các thanh hệ thống, chiều cao của Spacer sẽ bằng 0.

Khi IME đóng, các thay đổi sẽ diễn ra theo chiều ngược lại: Spacer bắt đầu mở rộng từ chiều cao bằng 0 sau khi imePadding() áp dụng ít hơn phía dưới của thanh hệ thống, cho đến khi cuối cùng Spacer khớp với chiều cao của phía dưới của thanh hệ thống sau khi IME hoàn tất quá trình chuyển động.

Hình 2. Cột lười từ cạnh đến cạnh có TextField.

Hành vi này được thực hiện thông qua hoạt động giao tiếp giữa tất cả các đối tượng sửa đổi windowInsetsPadding và có thể bị ảnh hưởng theo một số cách khác.

Modifier.consumeWindowInsets(insets: WindowInsets) cũng sử dụng phần lồng ghép theo cách tương tự như Modifier.windowInsetsPadding, nhưng không áp dụng phần lồng ghép đã sử dụng làm khoảng đệm. Điều này hữu ích khi kết hợp với các đối tượng sửa đổi kích thước phần lồng ghép, để cho biết với các thành phần cùng cấp rằng một số lượng phần lồng ghép nhất định đã được sử dụng:

Column(Modifier.verticalScroll(rememberScrollState())) {
    Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))

    Column(
        Modifier.consumeWindowInsets(
            WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
        )
    ) {
        // content
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
    }

    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}

Modifier.consumeWindowInsets(paddingValues: PaddingValues) hoạt động tương tự như phiên bản có đối số WindowInsets, nhưng lấy một PaddingValues tuỳ ý để sử dụng. Điều này rất hữu ích khi thông báo cho các thành phần con khi khoảng đệm hoặc khoảng cách được cung cấp bằng một cơ chế khác ngoài các đối tượng sửa đổi khoảng đệm lồng ghép, chẳng hạn như Modifier.padding thông thường hoặc các thành phần giãn cách có chiều cao cố định:

Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

Trong trường hợp cần các phần lồng ghép cửa sổ thô mà không cần sử dụng, hãy dùng trực tiếp các giá trị WindowInsets hoặc dùng WindowInsets.asPaddingValues() để trả về PaddingValues của các phần lồng ghép không bị ảnh hưởng bởi mức tiêu thụ. Tuy nhiên, do những lưu ý sau, bạn nên sử dụng đối tượng sửa đổi khoảng đệm phần lồng ghép cửa sổ và đối tượng sửa đổi kích thước phần lồng ghép cửa sổ bất cứ khi nào có thể.

Phần lồng ghép và các giai đoạn Jetpack Compose

Compose sử dụng các API cốt lõi AndroidX cơ bản để cập nhật và tạo ảnh động cho phần lồng ghép, sử dụng các API nền tảng cơ bản để quản lý phần lồng ghép. Do hành vi của nền tảng đó, phần lồng ghép có mối quan hệ đặc biệt với các giai đoạn của Jetpack Compose.

Giá trị của phần lồng ghép được cập nhật sau giai đoạn thành phần, nhưng trước giai đoạn bố cục. Điều này có nghĩa là việc đọc giá trị của phần lồng ghép trong thành phần thường sử dụng giá trị của phần lồng ghép muộn một khung hình. Các đối tượng sửa đổi tích hợp được mô tả trên trang này được tạo để trì hoãn việc sử dụng các giá trị của phần lồng ghép cho đến giai đoạn bố cục, điều này đảm bảo rằng các giá trị lồng ghép được sử dụng trên cùng một khung hình khi chúng được cập nhật.