Menampilkan konten layar penuh di aplikasi Anda dan menangani inset jendela di Compose

Platform Android bertanggung jawab untuk menggambar UI sistem, seperti {i>status bar<i} dan {i>navigation bar<i}. UI sistem ini ditampilkan, terlepas dari aplikasi yang digunakan pengguna.

WindowInsets memberikan informasi tentang UI sistem untuk memastikan aplikasi Anda menggambar di area yang benar dan UI Anda tidak tertutup oleh UI sistem.

Menampilkan tata letak layar penuh untuk menggambar di belakang kolom sistem
Gambar 1. Menuju layar penuh untuk menggambar di belakang kolom sistem.

Di Android 14 (level API 34) dan yang lebih rendah, UI aplikasi Anda tidak digambar di bawahnya kolom sistem dan potongan layar secara default.

Di Android 15 (API level 35) dan yang lebih tinggi, aplikasi Anda akan menggambar di bawah batang sistem dan menampilkan potongan setelah aplikasi menargetkan SDK 35. Hasilnya adalah pengalaman pengguna yang lancar dan memungkinkan aplikasi Anda untuk memanfaatkan sepenuhnya ruang jendela yang tersedia untuknya.

Menampilkan konten di belakang UI sistem disebut tampilan layar penuh. Di sini Anda akan mempelajari berbagai jenis inset, cara menampilkan tata letak layar penuh, dan cara menggunakan API inset untuk menganimasikan UI dan memastikan konten aplikasi Anda tidak terhalang oleh elemen UI sistem.

Dasar-dasar inset

Saat aplikasi menggunakan layar penuh, Anda harus memastikan bahwa konten dan interaksi penting tidak terhalang oleh UI sistem. Misalnya, jika sebuah tombol ditempatkan di belakang bilah navigasi, pengguna mungkin tidak dapat mengkliknya.

Ukuran UI sistem dan informasi tentang tempat penempatannya ditentukan melalui inset.

Setiap bagian dari UI sistem memiliki jenis inset yang sesuai yang menjelaskan ukurannya dan letaknya. Misalnya, {i>inset<i} status bar memberikan ukuran dan posisi {i>status bar<i}, sedangkan {i> inset<i} bilah navigasi menyediakan ukuran dan posisi bilah navigasi. Setiap jenis inset terdiri dari empat dimensi piksel: atas, kiri, kanan, dan bawah. Dimensi ini menentukan seberapa jauh UI sistem meluas dari sisi jendela aplikasi yang sesuai. Oleh karena itu, untuk menghindari tumpang-tindih dengan jenis UI sistem tersebut, UI aplikasi harus disisipkan dengan jumlah tersebut.

Jenis inset Android bawaan ini tersedia melalui WindowInsets:

WindowInsets.statusBars

Inset yang menjelaskan status bar. Ini adalah bilah UI sistem atas yang berisi ikon notifikasi dan indikator lainnya.

WindowInsets.statusBarsIgnoringVisibility

Inset status bar saat terlihat. Jika status bar saat ini disembunyikan (karena memasuki mode layar penuh imersif), inset status bar utama akan kosong, tetapi inset ini tidak akan kosong.

WindowInsets.navigationBars

Inset yang menjelaskan menu navigasi. Ini adalah panel UI sistem di sisi kiri, kanan, atau bawah perangkat, yang menjelaskan taskbar atau ikon navigasi. Hal ini dapat berubah saat runtime berdasarkan metode navigasi pilihan pengguna dan berinteraksi dengan taskbar.

WindowInsets.navigationBarsIgnoringVisibility

Inset menu navigasi saat terlihat. Jika menu navigasi saat ini disembunyikan (karena memasuki mode layar penuh imersif), inset menu navigasi utama akan kosong, tetapi inset ini akan berisi.

WindowInsets.captionBar

Inset yang menjelaskan dekorasi jendela UI sistem jika berada di jendela bentuk bebas, seperti kolom judul atas.

WindowInsets.captionBarIgnoringVisibility

Inset kolom teks saat terlihat. Jika panel teks saat ini disembunyikan, inset panel teks utama akan kosong, tetapi inset ini tidak akan kosong.

WindowInsets.systemBars

Gabungan inset panel sistem, yang mencakup status bar, menu navigasi, dan panel teks.

WindowInsets.systemBarsIgnoringVisibility

Inset panel sistem saat terlihat. Jika panel sistem saat ini disembunyikan (karena memasuki mode layar penuh imersif), inset panel sistem utama akan kosong, tetapi inset ini tidak akan kosong.

WindowInsets.ime

Inset yang menjelaskan jumlah ruang di bagian bawah yang ditempati keyboard software.

WindowInsets.imeAnimationSource

Inset yang menjelaskan jumlah ruang yang ditempati keyboard software sebelum animasi keyboard saat ini.

WindowInsets.imeAnimationTarget

Inset yang menjelaskan jumlah ruang yang akan ditempati keyboard virtual setelah animasi keyboard saat ini.

WindowInsets.tappableElement

Jenis inset yang menjelaskan informasi lebih mendetail tentang UI navigasi, yang memberikan jumlah ruang untuk "mengetuk" akan ditangani oleh sistem, dan bukan oleh aplikasi. Untuk menu navigasi transparan dengan navigasi gestur, beberapa elemen aplikasi dapat diketuk melalui UI navigasi sistem.

WindowInsets.tappableElementIgnoringVisibility

Inset elemen yang dapat diketuk saat terlihat. Jika elemen yang dapat diketuk saat ini disembunyikan (karena memasuki mode layar penuh imersif), inset elemen utama yang dapat diketuk akan kosong, tetapi inset ini tidak akan kosong.

WindowInsets.systemGestures

Inset yang mewakili jumlah inset tempat sistem akan mencegat gestur untuk navigasi. Aplikasi dapat menentukan secara manual penanganan gestur ini dalam jumlah terbatas melalui Modifier.systemGestureExclusion.

WindowInsets.mandatorySystemGestures

Subkumpulan gestur sistem yang akan selalu ditangani oleh sistem, dan yang tidak dapat dinonaktifkan melalui Modifier.systemGestureExclusion.

WindowInsets.displayCutout

Inset yang mewakili jumlah spasi yang diperlukan untuk menghindari tumpang-tindih dengan potongan layar (notch atau pinhole).

WindowInsets.waterfall

Inset yang mewakili area melengkung tampilan waterfall. Layar waterfall memiliki area melengkung di sepanjang tepi layar tempat layar mulai meliuk di sepanjang sisi perangkat.

Jenis ini diringkas dalam tiga jenis kata "aman" jenis inset yang memastikan dikaburkan:

Jenis inset "aman" ini melindungi konten dengan cara yang berbeda, berdasarkan inset platform yang mendasarinya:

  • Gunakan WindowInsets.safeDrawing untuk melindungi konten yang tidak boleh digambar di bawah UI sistem apa pun. Ini adalah penggunaan inset yang paling umum: untuk mencegah menggambar konten yang dikaburkan oleh UI sistem (sebagian atau sepenuhnya).
  • Gunakan WindowInsets.safeGestures untuk melindungi konten dengan gestur. Ini menghindari gestur sistem yang bertentangan dengan gestur aplikasi (seperti untuk gestur bawah spreadsheet, korsel, atau dalam {i>game<i}).
  • Gunakan WindowInsets.safeContent sebagai kombinasi dari WindowInsets.safeDrawing dan WindowInsets.safeGestures untuk memastikan konten tidak memiliki tumpang tindih visual dan tidak ada gestur yang tumpang tindih.

Penyiapan inset

Untuk memberi aplikasi kontrol penuh atas tempat menggambar konten, ikuti penyiapan ini langkah. Tanpa langkah-langkah ini, aplikasi Anda dapat menggambar warna hitam atau solid di belakang UI sistem, atau tidak menganimasikan secara sinkron dengan keyboard software.

  1. Targetkan SDK 35 atau yang lebih baru untuk menerapkan tata letak layar penuh di Android 15 dan yang lebih baru. Aplikasi Anda ditampilkan di belakang UI sistem. Anda dapat menyesuaikan UI aplikasi dengan menangani inset.
  2. Secara opsional, panggil enableEdgeToEdge() di Activity.onCreate(), yang memungkinkan aplikasi Anda menjadi layar penuh di versi Android sebelumnya.
  3. Tetapkan android:windowSoftInputMode="adjustResize" di entri AndroidManifest.xml Aktivitas Anda. Setelan ini memungkinkan aplikasi Anda menerima ukuran IME software sebagai inset, yang dapat Anda gunakan untuk mengisi dan menata konten secara tepat saat IME muncul dan menghilang di aplikasi Anda.

    <!-- 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">
    

API Compose

Setelah Aktivitas mengambil kontrol untuk menangani semua inset, Anda dapat menggunakan Compose API untuk memastikan konten tidak terkabur dan elemen yang dapat berinteraksi tumpang tindih dengan UI sistem. API ini juga menyinkronkan tata letak aplikasi Anda dengan perubahan inset.

Misalnya, ini adalah metode paling dasar untuk menerapkan inset ke konten seluruh aplikasi Anda:

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

    enableEdgeToEdge()

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

Cuplikan ini menerapkan inset jendela safeDrawing sebagai padding di sekitar seluruh konten aplikasi. Meskipun hal ini memastikan bahwa elemen yang dapat berinteraksi tidak tumpang-tindih dengan UI sistem, hal ini juga berarti bahwa tidak ada aplikasi yang akan menggambar di belakang UI sistem untuk mencapai efek dari tepi ke tepi. Untuk memaksimalkan penggunaan seluruh Anda perlu menyesuaikan tempat inset diterapkan di setiap layar atau komponen-per-komponen.

Semua jenis inset ini dianimasikan secara otomatis dengan animasi IME yang di-backport ke API 21. Secara luas, semua tata letak Anda yang menggunakan inset ini juga akan dianimasikan secara otomatis saat nilai inset berubah.

Ada dua cara utama untuk menggunakan jenis inset ini guna menyesuaikan tata letak Composable: pengubah padding dan pengubah ukuran inset.

Pengubah padding

Modifier.windowInsetsPadding(windowInsets: WindowInsets) menerapkan inset jendela tertentu sebagai padding, yang bertindak seperti Modifier.padding. Misalnya, Modifier.windowInsetsPadding(WindowInsets.safeDrawing) menerapkan {i>inset<i} gambar yang aman sebagai pelapis di keempat sisinya.

Ada juga beberapa metode utilitas bawaan untuk jenis inset yang paling umum. Modifier.safeDrawingPadding() adalah salah satu metode tersebut, setara dengan Modifier.windowInsetsPadding(WindowInsets.safeDrawing). Ada pengubah analog untuk jenis inset lainnya.

Pengubah ukuran inset

Pengubah berikut menerapkan jumlah inset jendela dengan menetapkan ukuran komponen menjadi ukuran inset:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Menerapkan sisi awal windowInsets sebagai lebar (seperti Modifier.width)

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Menerapkan sisi akhir windowInsets sebagai lebar (seperti Modifier.width)

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

Menerapkan sisi atas windowInsets sebagai tinggi (seperti Modifier.height)

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Menerapkan sisi bawah windowInsets sebagai tinggi (seperti Modifier.height)

Pengubah ini sangat berguna untuk mengubah ukuran Spacer yang menggunakan ukuran inset:

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

Konsumsi inset

Pengubah padding inset (windowInsetsPadding dan helper seperti safeDrawingPadding) otomatis menggunakan bagian inset yang diterapkan sebagai padding. Saat masuk lebih dalam ke hierarki komposisi, pengubah padding inset bertingkat dan pengubah ukuran inset mengetahui bahwa beberapa bagian inset telah digunakan oleh pengubah padding inset luar, dan menghindari penggunaan bagian inset yang sama lebih dari sekali yang akan menghasilkan terlalu banyak ruang tambahan.

Pengubah ukuran inset juga menghindari penggunaan bagian inset yang sama lebih dari sekali jika inset sudah digunakan. Namun, karena mengubah ukuran secara langsung, komponen ini tidak menggunakan inset.

Akibatnya, pengubah {i>nesting padding<i} secara otomatis mengubah jumlah padding yang diterapkan ke setiap composable.

Melihat contoh LazyColumn yang sama seperti sebelumnya, LazyColumn sedang diubah ukurannya oleh pengubah imePadding. Di dalam LazyColumn, item terakhir diukur agar memiliki tinggi bagian bawah panel sistem:

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

Saat IME ditutup, pengubah imePadding() tidak menerapkan padding, karena IME tidak memiliki tinggi. Karena pengubah imePadding() tidak menerapkan padding, tidak ada inset yang digunakan, dan tinggi Spacer akan menjadi ukuran sisi bawah panel sistem.

Saat IME terbuka, inset IME akan dianimasikan untuk menyesuaikan dengan ukuran IME, dan Pengubah imePadding() mulai menerapkan padding bawah untuk mengubah ukuran LazyColumn saat IME terbuka. Saat pengubah imePadding() mulai diterapkan padding bawah, model juga mulai memakai jumlah inset tersebut. Oleh karena itu, tinggi Spacer mulai berkurang, karena bagian spasi untuk batang sistem telah diterapkan oleh pengubah imePadding(). Setelah pengubah imePadding() menerapkan jumlah padding bawah yang lebih besar dari kolom sistem, tinggi Spacer adalah nol.

Saat IME ditutup, perubahan terjadi secara terbalik: Spacer mulai diperluas dari tinggi nol setelah imePadding() menerapkan lebih sedikit dari sisi bawah panel sistem, hingga akhirnya Spacer cocok dengan tinggi sisi bawah panel sistem setelah IME sepenuhnya dianimasikan.

Gambar 2. Kolom lambat dari tepi ke tepi dengan TextField.

Perilaku ini dicapai melalui komunikasi antara semua windowInsetsPadding pengubah, dan dapat dipengaruhi dalam beberapa cara.

Modifier.consumeWindowInsets(insets: WindowInsets) juga menggunakan inset dengan cara yang sama seperti Modifier.windowInsetsPadding, tetapi tidak menerapkan inset yang digunakan sebagai padding. Hal ini berguna dalam kombinasi dengan inset pengubah ukuran, untuk menunjukkan kepada saudara kandung bahwa sejumlah inset tertentu memiliki sudah dipakai:

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) berperilaku sangat mirip dengan versi dengan argumen WindowInsets, tetapi memerlukan PaddingValues arbitrer untuk digunakan. Hal ini berguna untuk menginformasikan turunan ketika padding atau spasi disediakan oleh beberapa mekanisme selain pengubah padding inset, seperti Modifier.padding biasa atau tinggi tetap pengatur jarak:

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

Jika inset jendela mentah diperlukan tanpa penggunaan, gunakan nilai WindowInsets secara langsung, atau gunakan WindowInsets.asPaddingValues() untuk menampilkan PaddingValues inset yang tidak terpengaruh oleh penggunaan. Namun, karena catatan di bawah, lebih suka menggunakan padding inset jendela dan pengubah ukuran inset jendela jika memungkinkan.

Fase Insets dan Jetpack Compose

Compose menggunakan API inti AndroidX dasar untuk mengupdate dan menganimasikan inset, yang menggunakan API platform dasar yang mengelola inset. Karena platform itu perilakunya, inset memiliki hubungan khusus dengan fase Jetpack Tulis.

Nilai inset diperbarui setelah fase komposisi, tetapi sebelum fase tata letak. Ini berarti bahwa membaca nilai inset dalam komposisi umumnya menggunakan nilai inset yang terlambat satu frame. Fitur bawaan pengubah yang dijelaskan di halaman ini dibuat untuk menunda penggunaan nilai inset hingga fase tata letak, yang memastikan bahwa nilai inset digunakan di {i>frame<i} yang sama saat diperbarui.

Animasi IME keyboard dengan WindowInsets

Anda dapat menerapkan Modifier.imeNestedScroll() ke penampung scroll untuk membuka dan menutup IME secara otomatis saat men-scroll ke bagian bawah kontainer.

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

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
                MyScreen()
            }
        }
    }
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon(imageVector = Icons.Filled.Add, contentDescription = "Add")
        }
    }
}

Animasi yang menampilkan elemen UI yang menggulir ke atas dan ke bawah untuk memberi jalan bagi keyboard
Gambar 3. Animasi IME.

Dukungan inset untuk Komponen Material 3

Untuk kemudahan penggunaan, banyak composable Material 3 bawaan (androidx.compose.material3) menangani inset sendiri, berdasarkan cara composable ditempatkan di aplikasi Anda sesuai dengan spesifikasi Material.

Composable penanganan sisipan

Di bawah ini adalah daftar dokumentasi Materi Komponen yang menangani inset secara otomatis.

Panel aplikasi

Penampung konten

Scaffold

Secara default, Scaffold menyediakan inset sebagai parameter paddingValues untuk Anda gunakan dan konsumsi. Scaffold tidak menerapkan inset ke konten; tanggung jawab ini menjadi milik Anda. Misalnya, untuk menggunakan inset ini dengan LazyColumn di dalam Scaffold:

Scaffold { innerPadding ->
    // innerPadding contains inset information for you to use and apply
    LazyColumn(
        // consume insets as scaffold doesn't do it by default
        modifier = Modifier.consumeWindowInsets(innerPadding),
        contentPadding = innerPadding
    ) {
        items(count = 100) {
            Box(
                Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(colors[it % colors.size])
            )
        }
    }
}

Mengganti inset default

Anda dapat mengubah parameter windowInsets yang diteruskan ke composable untuk mengonfigurasi perilaku composable. Parameter ini dapat berupa jenis inset jendela yang berbeda untuk diterapkan, atau dinonaktifkan dengan meneruskan instance kosong: WindowInsets(0, 0, 0, 0).

Misalnya, untuk menonaktifkan penanganan inset di LargeTopAppBar, tetapkan parameter windowInsets ke instance kosong:

LargeTopAppBar(
    windowInsets = WindowInsets(0, 0, 0, 0),
    title = {
        Text("Hi")
    }
)

Interop dengan inset sistem View

Anda mungkin perlu mengganti inset {i>default<i} ketika layar Anda memiliki View dan Tulis kode dalam hierarki yang sama. Dalam hal ini, Anda harus bersikap eksplisit dalam mana yang harus memakai inset, dan mana yang harus mengabaikannya.

Misalnya, jika tata letak terluar adalah tata letak Android View, Anda harus memakai inset di sistem View dan mengabaikannya untuk Compose. Atau, jika tata letak terluar adalah composable, Anda harus menggunakan inset di Compose, dan berikan padding pada composable AndroidView.

Secara default, setiap ComposeView menggunakan semua inset pada tingkat konsumsi WindowInsetsCompat. Untuk mengubah perilaku default ini, tetapkan ComposeView.consumeWindowInsets ke false.

Referensi