Wyświetlaj treść od krawędzi do krawędzi w aplikacji i obsługuj wstawki w oknie w narzędziu Compose

Platforma Android odpowiada za wyświetlanie interfejsu użytkownika systemu, takiego jak pasek stanu i pasek nawigacyjny. Ten interfejs systemu jest wyświetlany niezależnie od tego, z której aplikacji korzysta użytkownik.

WindowInsets zawiera informacje o interfejsie użytkownika systemu, aby aplikacja rysowała w odpowiednim miejscu i nie była przysłonięta przez interfejs systemu.

Wypełnianie od krawędzi do krawędzi, aby rysować za paskami systemu
Rysunek 1. Wypełnianie od krawędzi do krawędzi, aby rysować za paskami systemu.

W Androidzie 14 (poziom interfejsu API 34) i starszych interfejs użytkownika aplikacji nie jest domyślnie wyświetlany pod paskami systemowymi ani wycięciami na wyświetlaczu.

W Androidzie 15 (poziom interfejsu API 35) i nowszych aplikacja jest wyświetlana pod paskami systemu i wycięciami ekranu, gdy jest kierowana na pakiet SDK 35. Dzięki temu użytkownicy będą mogli korzystać z aplikacji w sposób bardziej płynny, a aplikacja będzie mogła w pełni wykorzystać dostępną przestrzeń okna.

Wyświetlanie treści za interfejsem systemu nazywa się wypełnianiem ekranu. Na tej stronie dowiesz się więcej o różnych typach wstawek, o tym, jak przejść od wyświetlania na całą szerokość ekranu, oraz o tym, jak używać interfejsów API wstawek do animowania interfejsu użytkownika i zapewnienia, że elementy interfejsu użytkownika systemu nie zasłaniają zawartości aplikacji.

Podstawy wstawek

Gdy aplikacja zajmuje cały ekran, musisz zadbać o to, aby ważne treści i interakcje nie były zasłonięte przez interfejs systemu. Jeśli na przykład przycisk znajduje się za paskiem nawigacji, użytkownik może nie mieć możliwości jego kliknięcia.

Rozmiar interfejsu systemu i informacje o jego położeniu są określane za pomocą insetów.

Każda część interfejsu systemu ma odpowiedni typ wstawki, który opisuje jej rozmiar i miejsce umieszczenia. Na przykład w ramkach paska stanu podajesz rozmiar i położenie paska stanu, a w ramkach paska nawigacji – rozmiar i położenie paska nawigacji. Każdy rodzaj wstawienia składa się z czterech wymiarów pikseli: góra, lewa, prawa i dół. Te wymiary określają, jak daleko interfejs systemu rozciąga się od odpowiednich boków okna aplikacji. Aby uniknąć nakładania się na ten typ interfejsu systemu, interfejs aplikacji musi być przesunięty o tę odległość.

Te wbudowane typy wstawek na Androidzie są dostępne w WindowInsets:

WindowInsets.statusBars

Wstawki opisujące paski stanu. To górne paski interfejsu systemu zawierające ikony powiadomień i inne wskaźniki.

WindowInsets.statusBarsIgnoringVisibility

Pasek stanu jest wsunięty, gdy jest widoczny. Jeśli paski stanu są obecnie ukryte (z powodu przejścia do trybu pełnoekranowego), wstawki głównego paska stanu będą puste, ale nie będą puste wstawki paska stanu.

WindowInsets.navigationBars

Wstawki opisujące paski nawigacyjne. Są to paski interfejsu systemu po lewej, prawej lub dolnej stronie urządzenia, które opisują pasek zadań lub ikony nawigacyjne. Mogą się one zmieniać w czasie działania w zależności od preferowanej przez użytkownika metody nawigacji oraz interakcji z paskiem aplikacji.

WindowInsets.navigationBarsIgnoringVisibility

Wstawki paska nawigacyjnego, gdy są widoczne. Jeśli paski nawigacyjne są obecnie ukryte (z powodu przejścia w tryb pełnoekranowy), wstawki głównego paska nawigacyjnego będą puste, ale nie będą puste.

WindowInsets.captionBar

Wstawka opisująca dekoracje okna interfejsu systemu w przypadku dowolnego okna, np. górny pasek tytułu.

WindowInsets.captionBarIgnoringVisibility

Pasek napisów jest wstawiony, gdy jest widoczny. Jeśli paski napisów są obecnie ukryte, wstawki głównego paska napisów będą puste, ale nie będą puste.

WindowInsets.systemBars

Zbiór elementów paska systemu, w tym paski stanu, paski nawigacyjne i paski nagłówków.

WindowInsets.systemBarsIgnoringVisibility

Wstawki paska systemowego, gdy są widoczne. Jeśli paski systemowe są obecnie ukryte (z powodu przejścia w tryb pełnoekranowy), wstawki głównego paska systemowego będą puste, ale nie będą puste.

WindowInsets.ime

Wstawki opisujące ilość miejsca na dole, którą zajmuje klawiatura ekranowa.

WindowInsets.imeAnimationSource

Wstawki opisujące ilość miejsca, którą zajmowała klawiatura programowa przed animacją bieżącej klawiatury.

WindowInsets.imeAnimationTarget

Wstawki opisujące ilość miejsca, jaką klawiatura programowa zajmie po bieżącej animacji klawiatury.

WindowInsets.tappableElement

Rodzaj wstawek opisujących bardziej szczegółowe informacje o interfejsie nawigacji, podając ilość miejsca, w którym „dotknięcia” będą obsługiwane przez system, a nie aplikację. W przypadku przezroczystych pasków nawigacji z gestami niektóre elementy aplikacji można dotknąć za pomocą interfejsu nawigacji systemu.

WindowInsets.tappableElementIgnoringVisibility

Elementy, które można dotknąć, są wstawione, gdy są widoczne. Jeśli elementy, które można kliknąć, są obecnie ukryte (z powodu przejścia w tryb pełnoekranowy), w ramkach głównych elementów, które można kliknąć, nie będzie żadnych elementów, ale same ramki będą widoczne.

WindowInsets.systemGestures

Wstawki reprezentujące liczbę wstawek, w których system przechwytuje gesty nawigacyjne. Aplikacje mogą ręcznie określić obsługę ograniczonej liczby tych gestów za pomocą Modifier.systemGestureExclusion.

WindowInsets.mandatorySystemGestures

Podzbiór gestów systemowych, które zawsze będą obsługiwane przez system i których nie można wyłączyć za pomocą Modifier.systemGestureExclusion.

WindowInsets.displayCutout

Wcięcia reprezentujące odstępy potrzebne, aby uniknąć nakładania się na wycięcie w ekranie (wycięcie lub małe otwory).

WindowInsets.waterfall

Wstawki przedstawiające zakrzywione obszary wyświetlania typu kaskada. Wyświetlacz kaskadowy ma zakrzywione obszary wzdłuż krawędzi ekranu w miejscach, w których ekran zaczyna się zawijać wzdłuż boków urządzenia.

Te typy są podsumowane przez 3 „bezpieczne” typy wstawek, które zapewniają, że treści nie są zasłonięte:

Te „bezpieczne” typy wstawek chronią treści na różne sposoby w zależności od wstawek na platformie:

  • Używaj narzędzia WindowInsets.safeDrawing do ochrony treści, które nie powinny być wyświetlane pod żadnym interfejsem systemu. To najczęstsze zastosowanie wstawienia, które zapobiegają rysowaniu treści zasłoniętych przez interfejs systemu (częściowo lub całkowicie).
  • Użyj WindowInsets.safeGestures, aby chronić treści za pomocą gestów. Zapobiega to konfliktom gestów systemowych z gestami w aplikacji (np. w przypadku karuzel na dole ekranu lub w grach).
  • Użyj właściwości WindowInsets.safeContent jako kombinacji właściwości WindowInsets.safeDrawing i WindowInsets.safeGestures, aby upewnić się, że treści nie nakładają się wizualnie i nie nakładają się za pomocą gestów.

Konfiguracja wkładek

Aby umożliwić aplikacji pełną kontrolę nad tym, gdzie może rysować treści, wykonaj te czynności konfiguracyjne. Bez wykonania tych czynności aplikacja może wyświetlać czarny kolor lub jednolite kolory za interfejsem systemu albo nie animować się synchronicznie z klawiaturą programową.

  1. Kieruj na pakiet SDK 35 lub nowszy, aby wymusić wyświetlanie bez ramki na urządzeniach z Androidem 15 lub nowszym. Aplikacja jest widoczna za interfejsem systemu. Możesz dostosować interfejs aplikacji, obsługując wstawki.
  2. Opcjonalnie możesz wywołać enableEdgeToEdge()Activity.onCreate(), co pozwoli aplikacji wyświetlać się bez ramki w poprzednich wersjach Androida.
  3. Ustaw wartość android:windowSoftInputMode="adjustResize" w polu AndroidManifest.xml w sekcji Aktywność. To ustawienie umożliwia aplikacji otrzymywanie rozmiaru programowego edytora IME w postaci wkładek, które można wykorzystać do odpowiedniego dopełnienia i rozłożenia treści, gdy dany IME pojawia się i znika w aplikacji.

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

Interfejsy API do tworzenia

Gdy aktywność przejmie kontrolę nad obsługą wszystkich wstawień, możesz użyć interfejsów API Compose, aby upewnić się, że treści nie są zasłonięte, a elementy, z którymi można wchodzić w interakcje, nie nakładają się na interfejs użytkownika systemu. Te interfejsy API synchronizują też układ aplikacji z zmianami w insetach.

Oto najprostsza metoda zastosowania wstawek do treści całej aplikacji:

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

    enableEdgeToEdge()

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

Ten fragment kodu stosuje wstawki okna safeDrawing jako wypełnienie wokół całej zawartości aplikacji. Dzięki temu elementy, z którymi można wchodzić w interakcję, nie będą nakładać się na interfejs systemu, ale oznacza to też, że żadna część aplikacji nie będzie narysowana za interfejsem systemu, aby uzyskać efekt obrazu sięgającego krawędzi. Aby w pełni wykorzystać cały ekran, musisz precyzyjnie określić, gdzie wstawiać wstawki na poszczególnych ekranach lub komponentach.

Wszystkie te typy wstawek są animowane automatycznie za pomocą animacji IME przeniesionych do interfejsu API 21. Co więcej, wszystkie układy korzystające z tych wstawienia są automatycznie animowane wraz ze zmianą wartości wstawienia.

Istnieją 2 podstawowe sposoby używania tych typów wstawek do dostosowywania układów kompozytowych: modyfikatory wypełnień i modyfikatory rozmiaru wstawki.

Modyfikatory wypełniania

Modifier.windowInsetsPadding(windowInsets: WindowInsets) stosuje podane wcięcia okna jako wypełnienie, działając tak samo jak Modifier.padding. Na przykład Modifier.windowInsetsPadding(WindowInsets.safeDrawing) stosuje bezpieczne wstawione rysunki jako wypełnienie po wszystkich 4 stronach.

Dostępnych jest też kilka wbudowanych metod pomocniczych dla najczęściej używanych typów wstawek. Modifier.safeDrawingPadding() to jedna z takich metod, równoważna funkcji Modifier.windowInsetsPadding(WindowInsets.safeDrawing). Istnieją analogiczne modyfikatory dla innych typów wstawienia.

Modyfikatory rozmiaru wnęki

Te modyfikatory określają wielkość wstawionych okien, ustawiając rozmiar komponentu na rozmiar wstawionych okien:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Stosuje stronę początkową elementów windowInsets jako szerokość (taką jak Modifier.width)

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Stosuje końcową stronę elementów windowInsets jako szerokość (np. Modifier.width)

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

Stosuje górną stronę zawijania okna jako wysokość (np. Modifier.height).

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Dolny bok windowInsets jest stosowany jako wysokość (np. Modifier.height).

Te modyfikatory są szczególnie przydatne do określania rozmiaru elementu Spacer, który zajmuje przestrzeń wstawienia:

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

Konsumpcja w ramce

Modyfikatory wkładki wypełniającej (windowInsetsPadding i elementy pomocnicze, takie jak safeDrawingPadding) automatycznie wykorzystują część wkładki, która jest stosowana jako wypełnienie. Wchodząc głębiej w drzewo kompozycji, wiesz, że wpisane modyfikatory wypełniania wgłębienia i modyfikatory rozmiaru wgłębienia wiedzą, że część wgłębień została już wykorzystana przez zewnętrzne modyfikatory wypełniania wgłębienia. Unikaj używania tej samej części wgłębień więcej niż raz, ponieważ spowodowałoby to zbyt dużo dodatkowej przestrzeni.

Zmienne rozmiaru wstawek również zapobiegają wielokrotnemu używaniu tej samej części wstawek, jeśli zostały one już wykorzystane. Ponieważ jednak zmieniają one bezpośrednio swój rozmiar, nie wykorzystują wstawek.

W efekcie modyfikatory zagnieżdżania dopełnienia automatycznie zmieniają ilość dopełnienia zastosowanego do każdego elementu kompozycyjnego.

W tym samym przykładzie LazyColumn rozmiar obiektu LazyColumn jest zmieniany przez modyfikator imePadding. W elementach LazyColumn ostatni element ma wysokość odpowiadającą wysokości dolnej krawędzi pasków systemu:

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

Gdy IME jest zamknięte, modyfikator imePadding() nie stosuje wypełnienia, ponieważ IME nie ma wysokości. Ponieważ modyfikator imePadding() nie stosuje żadnych wypełnień, nie są używane żadne wstawki, a wysokość elementu Spacer będzie odpowiadać rozmiarowi dolnej strony pasków systemu.

Gdy otworzy się klawiatura, jej wstawione elementy animują się, aby dopasować się do jej rozmiaru, a modyfikator imePadding() zacznie stosować wypełnienie dolne, aby zmienić rozmiar LazyColumn w miarę otwierania klawiatury. Gdy modyfikator imePadding() zaczyna stosować dopełnienie u dołu, zaczyna też zużywać tę liczbę wkładek. W związku z tym wysokość Spacer zaczyna się zmniejszać, ponieważ część odstępów między elementami systemu została już zastosowana przez modyfikator imePadding(). Gdy modyfikator imePadding() zastosuje dopełnienie dolne większe niż paski systemu, wysokość Spacer wynosi 0.

Po zamknięciu edytora IME zmiany zachodzą odwrotnie: gdy edytor imePadding() jest mniejszy niż dolna część pasków systemowych, Spacer zaczyna się rozwijać od zera, aż w końcu Spacer będzie się zgadzać z wysokością dolnej krawędzi słupków systemowych, gdy edytor IME zostanie całkowicie animowany.

Rysunek 2. Kolumna z automatycznym wypełnianiem od krawędzi do krawędzi z wartością TextField.

Takie zachowanie jest osiągane dzięki komunikacji między wszystkimi modyfikatorami windowInsetsPadding i może być modyfikowane na kilka innych sposobów.

Modifier.consumeWindowInsets(insets: WindowInsets) również wykorzystuje wstawione elementy w taki sam sposób jak Modifier.windowInsetsPadding, ale nie stosuje wstawionych elementów jako wypełnienia. Jest to przydatne w połączeniu z modyfikatorami rozmiaru wstawienia, aby zasygnalizować rodzeństwo, że pewna liczba wstawienia została już wykorzystana:

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) działa bardzo podobnie do wersji z argumentem WindowInsets, ale przyjmuje dowolną wartość PaddingValues. Jest to przydatne, gdy dzieci muszą wiedzieć, że wypełnienie lub odstępy są zapewniane przez inny mechanizm niż modyfikatory wbudowanego wypełniania, takie jak zwykłe Modifier.padding lub stałe odstępy:

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

W sytuacjach, gdy potrzebne są nieprzetworzone wstawki okienne bez konsumpcji, używaj bezpośrednio wartości WindowInsets lub funkcji WindowInsets.asPaddingValues(), aby zwrócić PaddingValues wektorów, na które nie ma wpływu wykorzystanie. Ze względu na poniższe zastrzeżenia lepiej w miarę możliwości używaj modyfikatorów dopełnienia wstawienia okien i modyfikatorów rozmiaru wstawienia okien.

Ramki i fazy Jetpack Compose

Compose używa podstawowych interfejsów API AndroidX do aktualizowania i animowania wgłębień, które korzystają z podstawowych interfejsów API platformy do zarządzania wgłębieniami. Ze względu na to zachowanie platformy wstawki mają szczególny związek z etazami Jetpacka Compose.

Wartości wstawek są aktualizowane po fazie tworzenia kompozycji, ale przed fazą układu. Oznacza to, że odczyt wartości wstawek w kompozycji wykorzystuje zazwyczaj wartość wstawek, która jest o 1 klatka później. Wbudowane modyfikatory opisane na tej stronie są zaprojektowane tak, aby opóźniać używanie wartości w ramkach do fazy układu. Dzięki temu wartości w ramkach są używane w tym samym ujęciu, w którym są aktualizowane.

Animacje klawiatury IME z WindowInsets

Możesz zastosować Modifier.imeNestedScroll() do ruchomego kontenera, aby automatycznie otwierać i zamykać IME, gdy przewijasz do dołu.

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")
        }
    }
}

Animacja pokazująca element interfejsu przewijany w górę i w dół, aby zrobić miejsce na klawiaturę
Rysunek 3. i animacje IME.

Obsługa wstawionych komponentów Material 3

Dla ułatwienia wiele wbudowanych komponentów kompozytowych Material 3 (androidx.compose.material3) samodzielnie obsługuje wstawki na podstawie tego, jak komponenty te są umieszczone w aplikacji zgodnie ze specyfikacją Material.

Obsługa funkcji kompozytowych w ramce

Poniżej znajduje się lista komponentów Material Design, które automatycznie obsługują wstawki.

Paski aplikacji

Kontenery treści

Rusztowanie

Domyślnie funkcja Scaffold udostępnia wstawki jako parametr paddingValues, który możesz wykorzystać. Scaffold nie stosuje wstawek do treści; ta odpowiedzialność spoczywa na użytkowniku. Aby na przykład użyć tych wstawek z LazyColumn w ramach 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])
            )
        }
    }
}

Zastępowanie domyślnych wstawek

Możesz zmienić parametr windowInsets przekazywany do funkcji kompozycyjnej, aby skonfigurować jej działanie. Ten parametr może być innym typem wstawionego okna, który ma być zastosowany, lub może być wyłączony przez podanie pustej instancji: WindowInsets(0, 0, 0, 0).

Aby na przykład wyłączyć obsługę wstawienia do obiektu LargeTopAppBar, ustaw dla parametru windowInsets pustą instancję:

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

Współdziałanie z wstawkami systemu View

Może być konieczne zastąpienie domyślnych wkładek, gdy ekran zawiera zarówno widoki, jak i kod tworzenia wiadomości w tej samej hierarchii. W takim przypadku musisz wyraźnie określić, która z nich powinna używać wstawek, a która powinna je ignorować.

Jeśli na przykład najszerszy układ jest układem Android View, powinieneś używać wstawek w systemie View i ignorować je w Compose. Jeśli natomiast zewnętrzny układ jest składanym elementem, musisz użyć w Compose wbudowanych elementów i odpowiednio wypełnić AndroidView.

Domyślnie każdy element ComposeView zużywa wszystkie wstawione elementy na poziomie zużycia WindowInsetsCompat. Aby zmienić to domyślne działanie, ustaw wartość ComposeView.consumeWindowInsets na false.

Ochrona paska systemu

Gdy aplikacja będzie kierowana na pakiet SDK 35 lub nowszy, wyświetlanie bez ramki będzie wymuszane. Pasek stanu systemu i paski nawigacji przy użyciu gestów są przezroczyste, ale pasek nawigacji z 3 przyciskami jest półprzezroczysty.

Aby usunąć domyślną, półprzezroczystą ochronę w tle nawigacji przy użyciu 3 przycisków, ustaw opcję Window.setNavigationBarContrastEnforced na false.

Materiały