Wstawienia okien w oknie Utwórz

Platforma Android odpowiada za rysowanie interfejsu systemu, w tym paska stanu i nawigacji. Interfejs ten wyświetla się niezależnie od tego, jakiej aplikacji używa użytkownik. WindowInsets podaje informacje o interfejsie systemu, aby zapewnić, że aplikacja wyświetla się we właściwym obszarze, a interfejs nie jest zasłonięty.

Od krawędzi do krawędzi w tle słupki systemowe
Rysunek 1. Od krawędzi do krawędzi w stosunku do słupków systemowych

Domyślnie interfejs aplikacji jest ograniczony do interfejsu systemowego, np. paska stanu i nawigacji. Dzięki temu zawartość aplikacji nie jest zasłonięta przez elementy interfejsu systemu.

Zalecamy jednak, aby aplikacje wyświetlały się w tych obszarach, w których wyświetla się też interfejs systemu. Zapewni to większy komfort użytkowników i umożliwi pełne wykorzystanie dostępnej przestrzeni okna aplikacji. Pozwala to też aplikacjom animować się razem z interfejsem systemu, zwłaszcza przy wyświetlaniu i ukrywaniu klawiatury oprogramowania.

Włączenie wyświetlania treści w tych regionach i za pomocą interfejsu systemowego jest nazywane przesyłaniem treści od krawędzi do krawędzi. Na tej stronie poznasz różne typy wstawionych wkładek, dowiesz się, jak zacząć korzystać z wbudowanych wzorów i jak używać wbudowanych interfejsów API do animowania UI i unikania zasłaniania części aplikacji.

Podstawy wstawiania

Gdy aplikacja jest w całości dopracowana, jej ważne treści i interakcje nie powinny być zasłonięte przez interfejs systemu. Jeśli na przykład przycisk znajduje się za paskiem nawigacyjnym, użytkownik może nie być w stanie go kliknąć.

Rozmiar interfejsu systemu i informacje o tym, gdzie się znajduje, są określane za pomocą zestawów.

Każda część interfejsu systemu ma odpowiedni typ wstawienia, który opisuje jej rozmiar i miejsce umieszczenia. Na przykład wcięcia paska stanu określają rozmiar i pozycję paska stanu, a wstawki paska nawigacyjnego – rozmiar i pozycję paska nawigacyjnego. Każdy typ 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ć wstawiony przez tę wartość.

Te wbudowane typy wkładek na Androida są dostępne w WindowInsets:

WindowInsets.statusBars

Wstawki opisujące paski stanu. Są to górne paski interfejsu użytkownika, które zawierają ikony powiadomień i inne wskaźniki.

WindowInsets.statusBarsIgnoringVisibility

Pasek stanu wbudowuje się, gdy jest widoczny. Jeśli paski stanu są obecnie ukryte (z powodu włączenia pełnego ekranu), wstawki głównego paska stanu będą puste, ale nie będą puste.

WindowInsets.navigationBars

Wstawki opisujące paski nawigacyjne. Są to paski interfejsu systemu z lewej, prawej lub u dołu, które opisują pasek aplikacji lub ikony nawigacji. 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

Pasek nawigacyjny jest wstawiane, gdy są widoczne. Jeśli paski nawigacyjne są obecnie ukryte (z powodu włączenia pełnego ekranu), 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 wstawiane, gdy są widoczne. Jeśli paski napisów są obecnie ukryte, główne wcięcia pasków napisów będą puste, ale te wstawki nie będą puste.

WindowInsets.systemBars

Połączenie wcięć pasków systemowych, które obejmują paski stanu, nawigacji i napisów.

WindowInsets.systemBarsIgnoringVisibility

Pasek systemowy jest wstawiane, gdy są widoczne. Jeśli paski systemowe są obecnie ukryte (z powodu włączenia się w tryb pełnego ekranu), główne wcięcia pasków systemowych będą puste, ale nie będą puste.

WindowInsets.ime

Wstawki opisujące ilość miejsca na dole zajmowanego przez klawiaturę programową.

WindowInsets.imeAnimationSource

Wstawki opisujące ilość miejsca zajmowanego przez klawiaturę programową przed bieżącą animacją klawiatury.

WindowInsets.imeAnimationTarget

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

WindowInsets.tappableElement

Rodzaj wkładek opisujących bardziej szczegółowe informacje o interfejsie nawigacji, podające ilość miejsca, w którym „dotknięcia” zajmuje system, a nie aplikacja. W przypadku przezroczystych pasków nawigacyjnych z nawigacją przy użyciu gestów niektóre elementy aplikacji można klikać w interfejsie systemowym.

WindowInsets.tappableElementIgnoringVisibility

Elementy do kliknięcia pojawiają się, gdy są widoczne. Jeśli elementy do kliknięcia są obecnie ukryte (z powodu przejścia w pojemny tryb pełnoekranowy), główne wstawki z elementami do kliknięcia będą puste, ale nie będą puste.

WindowInsets.systemGestures

Wstawki reprezentujące liczbę wektorów, w których system przechwytuje gesty na potrzeby nawigacji. Aplikacje mogą ręcznie określać sposób obsługi ograniczonej liczby tych gestów za pomocą Modifier.systemGestureExclusion.

WindowInsets.mandatorySystemGestures

Zestaw gestów systemowych, które będą zawsze obsługiwane przez system i z których nie można zrezygnować w Modifier.systemGestureExclusion.

WindowInsets.displayCutout

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

WindowInsets.waterfall

Wgłębienia reprezentujące zakrzywione obszary wodospadu. 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 podsumowujemy za pomocą 3 „bezpiecznych” typów wstawienia, które zapewniają, że treść nie jest zasłonięta:

Te „bezpieczne” typy wstawionych wkładek chronią treści na różne sposoby w zależności od leżących u nich elementów platformy:

  • 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 pozwala zapobiegać rysowaniu treści zasłoniętych przez interfejs systemu (częściowo lub całkowicie).
  • Korzystaj z WindowInsets.safeGestures, aby chronić treści za pomocą gestów. Dzięki temu gesty systemu nie kolidują z gestami w aplikacji (np. w arkuszach u dołu, w karuzeli czy w grach).
  • Użyj WindowInsets.safeContent jako kombinacji funkcji WindowInsets.safeDrawing i WindowInsets.safeGestures, aby treści nie nakładały się na siebie ani nie nakładały się na gesty.

Konfiguracja wkładek

Aby zapewnić aplikacji pełną kontrolę nad pobieraniem treści, wykonaj te czynności konfiguracyjne. Jeśli nie wykonasz tych czynności, aplikacja może wyświetlać czarne lub jednolite kolory za interfejsem systemu albo nie animować się synchronicznie z klawiaturą oprogramowania.

  1. Zadzwoń pod numer enableEdgeToEdge() w: Activity.onCreate. W ramach tego wywołania aplikacja wyświetla się za interfejsem systemu. Aplikacja będzie wtedy kontrolować, jak te wstawki są używane do dostosowywania interfejsu użytkownika.
  2. Ustaw android:windowSoftInputMode="adjustResize" w wpisie o aktywności AndroidManifest.xml. To ustawienie umożliwia aplikacji otrzymywanie rozmiaru programowego edytora IME w postaci wkładek, które można wykorzystać do odpowiedniego dopełnienia i rozmieszczenia treści, gdy 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 tworzenia wiadomości

Gdy aktywność przejmie kontrolę nad obsługą wszystkich wstawienia, możesz używać interfejsów API tworzenia, aby mieć pewność, że treść nie jest zasłonięta, a elementy interaktywne nie nakładają się na interfejs systemu. Te interfejsy API synchronizują też układ aplikacji ze zmianami wstawionymi.

Jest to na przykład najprostsza metoda stosowania wstawienia 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 wcięcia okien safeDrawing jako dopełnienie wokół całej zawartości aplikacji. Dzięki temu elementy interaktywne nie nakładają się na interfejs systemu, ale żadna aplikacja nie będzie się zasłaniać interfejsem systemu, aby uzyskać efekt „od krawędzi do krawędzi”. Aby w pełni wykorzystać całe okno, musisz dostosować miejsca, w których wcięcia są stosowane, osobno na każdym ekranie, czy krok po kroku.

Wszystkie te typy wstawienia reklamy są animowane automatycznie, korzystając z animacji IME przywróconych do interfejsu API 21. Co więcej, wszystkie układy używające tych wstawienia są automatycznie animowane wraz ze zmianą wartości wstawienia.

Istnieją 2 główne sposoby używania tych rodzajów wstawienia do dostosowywania układów kompozycyjnych: modyfikatory dopełnienia i modyfikatory rozmiaru wstawienia.

Modyfikatory dopełnienia

Modifier.windowInsetsPadding(windowInsets: WindowInsets) stosuje podane wstawki okna jako dopełnienie, działając w sposób taki jak Modifier.padding. Na przykład Modifier.windowInsetsPadding(WindowInsets.safeDrawing) stosuje bezpieczne wstawki do rysowania jako dopełnienie ze wszystkich 4 stron.

Istnieje też kilka wbudowanych metod narzędzia dla najpopularniejszych typów wstawienia. Modifier.safeDrawingPadding() jest jedną z takich metod, równoważną z metodą Modifier.windowInsetsPadding(WindowInsets.safeDrawing). Istnieją analogiczne modyfikatory dla innych typów wstawienia.

Modyfikatory rozmiaru wstawionego

Te modyfikatory powodują zastosowanie liczby wstawionych okien przez ustawienie rozmiaru komponentu na wcięcia:

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ę wstawki okna (np. Modifier.height) jako wysokość

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Stosuje dolną część elementu windowInsets jako wysokość (np. Modifier.height)

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

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

Wykorzystanie wbudowanej

Wstawione modyfikatory dopełnienia (windowInsetsPadding i elementy pomocnicze, takie jak safeDrawingPadding) automatycznie używają części wkładek, które są stosowane jako dopełnienie. Wgląd w drzewo kompozycji zagnieżdżony jest tylko z zagnieżdżonych modyfikatorów dopełnienia i rozmiaru wstawienia, które wiedzą, że niektóre części i rozmiaru insetów zostały już wykorzystane przez zewnętrzne modyfikatory dopełnienia wstawionego. Unikaj więc używania tej samej części wkładek więcej niż raz, bo spowodowałoby to uzyskanie zbyt wielu dodatkowych przestrzeni.

Modyfikatory rozmiaru wstawienia nie używają też tej samej części wstawienia więcej niż raz, jeśli wstawienia zostały już wykorzystane. Nie używają jednak wkładek, ponieważ zmieniają swój rozmiar bezpośrednio.

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

W przypadku tego samego przykładu LazyColumn co poprzednio widać, że rozmiar elementu LazyColumn jest zmieniany przez modyfikator imePadding. Rozmiar ostatniego elementu w elemencie LazyColumn to wysokość dolnych słupków systemowych:

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

Po zamknięciu edytora IME modyfikator imePadding() nie stosuje dopełnienia, ponieważ nie ma on wysokości. Modyfikator imePadding() nie stosuje dopełnienia, więc nie są stosowane żadne wstawki, a wysokość elementu Spacer będzie taka sama jak rozmiar dolnej krawędzi słupków systemowych.

Po otwarciu edytora IME wstawki są animowane zgodnie z jego rozmiarem, a modyfikator imePadding() zaczyna stosować dopełnienie u dołu, aby zmienić rozmiar znacznika LazyColumn po jego otwarciu. Gdy modyfikator imePadding() zaczyna stosować dopełnienie u dołu, zaczyna też zużywać tę liczbę wkładek. Dlatego wysokość elementu Spacer zaczyna się zmniejszać, ponieważ część odstępów między słupkami systemowymi została już zastosowana przez modyfikator imePadding(). Gdy modyfikator imePadding() zastosuje dopełnienie u dołu większe niż słupki systemowe, wysokość elementu Spacer będzie wynosić 0.

Po zamknięciu edytora IME zmiany zachodzą odwrotnie: gdy edytor imePadding() jest mniejszy od dolnej krawędzi 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. Leniwa kolumna od krawędzi do krawędzi z atrybutem TextField

Dzieje się tak dzięki komunikacji między wszystkimi modyfikatorami windowInsetsPadding i można na nie wpływać kilkoma innymi sposobami.

Funkcja Modifier.consumeWindowInsets(insets: WindowInsets) również używa wstawienia w taki sam sposób jak Modifier.windowInsetsPadding, ale nie stosuje ich jako dopeł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))
}

Funkcja Modifier.consumeWindowInsets(paddingValues: PaddingValues) działa bardzo podobnie do wersji z argumentem WindowInsets, ale korzysta z arbitrażu PaddingValues. Jest to przydatne, gdy chcesz poinformować dzieci o tym, że dopełnienie lub odstępy są zapewniane przez inny mechanizm niż wstawiane modyfikatory dopełnienia, takie jak zwykłe Modifier.padding lub odstępy o stałej wysokości:

@OptIn(ExperimentalLayoutApi::class)
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.

Etapy wektorów i Jetpack Compose

Compose używa podstawowych interfejsów API AndroidaX do aktualizowania i animowania wektorów, które korzystają z bazowych interfejsów API platformy do zarządzania wstawieniami. Ze względu na takie działanie platformy zestawy wektorowe mają specjalny związek z fazami Jetpack Compose.

Wartość wstawienia jest aktualizowana po fazie kompozycji, ale przed fazą układu. Oznacza to, że odczyt wartości wstawienia w kompozycji zwykle używa wartości wstawienia, która jest o jedną klatkę opóźniona. Wbudowane modyfikatory opisane na tej stronie są skonstruowane tak, aby opóźniały korzystanie z wartości zestawu do fazy układu, dzięki czemu wartości wstawienia są używane w tej samej klatce, w której są aktualizowane.

Animacje klawiaturowe IME w języku WindowInsets

Możesz zastosować Modifier.imeNestedScroll() do przewijanego kontenera, aby automatycznie otwierać i zamykać edytor IME podczas przewijania 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 przedstawiająca element interfejsu przewijany w górę i w dół w miejsce na klawiaturę

Rysunek 1. Animacje IME

Wbudowana obsługa komponentów Material 3

Aby ułatwić sobie korzystanie, wiele wbudowanych uchwytów kompozycyjnych Material 3 (androidx.compose.material3) dostaje się samodzielnie w zależności od tego, w jaki sposób elementy kompozycyjne są rozmieszczone w aplikacji zgodnie ze specyfikacją Material.

Elementy kompozycyjne obsługi wstawki

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

Paski aplikacji

Kontenery treści

Ruszt

Domyślnie Scaffold udostępnia wektory jako parametr paddingValues, które można wykorzystać i wykorzystać. Scaffold nie stosuje wstawienia do treści. To Ty ponosisz odpowiedzialność. Aby np. wykorzystać te wektory z parametrem LazyColumn wewnątrz właściwości 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ąp domyślne wstawki

Możesz zmienić parametr windowInsets przekazywany do funkcji kompozycyjnej, aby skonfigurować jej działanie. Może to być inny typ wstawienia okna lub wyłączenie go przez przekazanie 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")
    }
)

Interakcja z wektorami systemowymi widoku danych

Może być konieczne zastąpienie domyślnych wstawionych elementów, gdy ekran zawiera zarówno widoki, jak i kod tworzenia wiadomości w tej samej hierarchii. W takiej sytuacji musisz wyraźnie wskazać, który z nich ma korzystać z wstawienia, a który je ignorować.

Jeśli na przykład Twój najbardziej zewnętrzny układ to układ Android View, podczas tworzenia wiadomości używaj wstawienia w systemie widoku i zignoruj je. Jeśli Twój najbardziej zewnętrzny układ jest kompozycyjny, użyj też zestawów instalacyjnych w funkcji Compose i dodaj odpowiednie elementy kompozycyjne AndroidView.

Domyślnie każdy zasób ComposeView wykorzystuje wszystkie wstawki na poziomie wykorzystania (WindowInsetsCompat). Aby zmienić to domyślne działanie, ustaw opcję ComposeView.consumeWindowInsets na false.

Zasoby

  • Teraz w Androidzie – w pełni funkcjonalna aplikacja na Androida stworzona w całości przy użyciu Kotlin i Jetpack Compose.