Wstawienia okien w oknie Utwórz

Platforma Androida odpowiada za rysowanie interfejsu systemu, np. paska stanu i paska nawigacyjnego. Ten interfejs systemowy wyświetla się niezależnie od aplikacji używanej przez użytkownika. WindowInsets podaje informacje o interfejsie systemu, dzięki czemu aplikacja wyświetla się w odpowiednim miejscu i nie jest przysłonięta.

Rysowanie od krawędzi do krawędzi za pasami systemowymi
Rysunek 1. Przesuwanie od krawędzi do krawędzi, aby rysować za pasami systemowymi

Domyślnie interfejs użytkownika aplikacji jest ograniczony do interfejsu systemu, np. paska stanu i paska nawigacyjnego. Dzięki temu zawartość aplikacji nie będzie zasłonięta przez elementy interfejsu systemu.

Zalecamy jednak, aby aplikacje mogły wyświetlać się w tych obszarach, gdzie pojawia się także interfejs systemu. Zwiększa to wygodę użytkowników i pozwala w pełni wykorzystać dostępną przestrzeń w oknie. Dzięki temu aplikacje mogą być animowane razem z interfejsem systemowym, zwłaszcza gdy pokazujesz i ukrywasz klawiaturę oprogramowania.

Włączenie opcji wyświetlania treści w tych regionach i wyświetlania treści za UI systemu nosi nazwę odstępu od krawędzi do krawędzi. Na tej stronie poznasz różne typy wstawionych elementów, dowiesz się, jak włączyć wypełnianie od krawędzi do krawędzi, a także dowiesz się, jak używać wstawionych interfejsów API do animowania UI i unikania zasłaniania części aplikacji.

Podstawy wstawki

Gdy aplikacja zacznie działać od krawędzi do krawędzi, 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 jest umieszczony za paskiem nawigacyjnym, użytkownik może nie być w stanie go kliknąć.

Rozmiar interfejsu systemowego i informacje o jego umieszczenia są określane za pomocą insets.

Każda część interfejsu systemowego ma odpowiedni typ wstawienia opisującego jej rozmiar i miejsce, w którym się znajduje. Na przykład wstawki wskazują rozmiar i pozycję paska stanu, a wstawki z nim określają rozmiar i pozycję paska nawigacyjnego. Każdy z nich składa się z 4 wymiarów w pikselach: góra, lewa, prawa i dół. Wymiary te określają, jak daleko od odpowiednich stron okna aplikacji rozciąga się interfejs systemu. Aby uniknąć nakładania się tego typu UI systemu, interfejs aplikacji musi być osadzony o odpowiednią kwotę.

W WindowInsets dostępne są te wbudowane typy wbudowanych funkcji Androida:

WindowInsets.statusBars

Elementy wstawienia opisujące paski stanu. Są to górne paski interfejsu systemu, zawierające ikony powiadomień i inne wskaźniki.

WindowInsets.statusBarsIgnoringVisibility

Wsunięcia paska stanu wskazują, kiedy są widoczne. Jeśli obecnie paski stanu są ukryte (z powodu przejścia w tryb pełnego ekranu), elementy główne na pasku stanu będą puste, ale te elementy nie będą.

WindowInsets.navigationBars

Elementy wstawiane z opisami pasków nawigacyjnych. To paski interfejsu systemu, które znajdują się po lewej, prawej lub dolnej części urządzenia, i 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 i interakcji z paskiem zadań.

WindowInsets.navigationBarsIgnoringVisibility

Wstawki paska nawigacyjnego w czasie, gdy są widoczne. Jeśli paski nawigacyjne są obecnie ukryte (z powodu przejścia w tryb pełnego ekranu), elementy z wbudowanymi paskami nawigacyjnymi będą puste, ale te elementy nie będą.

WindowInsets.captionBar

Fragment opisujący dekorację okna interfejsu systemu w oknie swobodnym, np. górny pasek tytułu.

WindowInsets.captionBarIgnoringVisibility

Pasek napisów jest wsunięty, gdy mają być widoczne. Jeśli obecnie paski napisów są ukryte, te elementy wstawione na głównym pasku napisów będą puste, ale te elementy nie będą.

WindowInsets.systemBars

Połączenie elementów paska nawigacyjnego, w tym pasków stanu, paska nawigacyjnego i paska napisów.

WindowInsets.systemBarsIgnoringVisibility

Wstawki z paska systemowego informujące o tym, kiedy są widoczne. Jeśli paski systemowe są obecnie ukryte (z powodu przejścia w tryb pełnego ekranu), elementy z wbudowanymi paskami systemu będą puste, ale te elementy nie będą puste.

WindowInsets.ime

Wstawki opisujące ilość miejsca u dołu zajmowanego przez klawiaturę programową.

WindowInsets.imeAnimationSource

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

WindowInsets.imeAnimationTarget

Wstawki opisujące ilość miejsca zajmowanego przez klawiaturę programową po bieżącej animacji klawiatury.

WindowInsets.tappableElement

Rodzaj wstawionych elementów opisujących bardziej szczegółowe informacje o interfejsie nawigacji, które określają ilość miejsca zajmowanego przez system, a nie aplikację. W przypadku przezroczystych pasków nawigacyjnych z nawigacją przy użyciu gestów niektóre elementy aplikacji można kliknąć w interfejsie nawigacji w systemie.

WindowInsets.tappableElementIgnoringVisibility

Elementy, które można kliknąć, określają, kiedy są widoczne. Jeśli elementy, które można kliknąć, są obecnie ukryte (z powodu wejścia w tryb pełnego ekranu), elementy wstawione z elementami do klikania będą puste, ale te elementy nie będą puste.

WindowInsets.systemGestures

Wstawki reprezentujące liczbę wstawienia, w których system przechwytuje gesty podczas nawigacji. W aplikacjach można ręcznie określić obsługę ograniczonej liczby tych gestów na stronie Modifier.systemGestureExclusion.

WindowInsets.mandatorySystemGestures

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

WindowInsets.displayCutout

Odstępy odpowiadające odstępom wymaganym, by uniknąć nakładania się na wycięcie wyświetlacza (wycięcie w skale lub otwór z małym otworem).

WindowInsets.waterfall

Wstawki reprezentujące zakrzywione obszary kaskady. Ekran kaskadowy ma zakrzywione obszary wzdłuż krawędzi ekranu, w których zaczyna się zwijać wzdłuż boków urządzenia.

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

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

  • WindowInsets.safeDrawing służy do ochrony treści, które nie powinny być widoczne pod interfejsem systemu. Najczęściej są one stosowane do zapobiegania rysowaniu treści zasłanianych przez interfejs systemu (częściowo lub całkowicie).
  • Aby chronić treści za pomocą gestów, użyj WindowInsets.safeGestures. Pozwala to uniknąć sprzeczności gestów systemowych z gestami aplikacji (np. w dolnych arkuszach, karuzeli czy w grach).
  • Użyj WindowInsets.safeContent jako kombinacji właściwości WindowInsets.safeDrawing i WindowInsets.safeGestures, aby treści nie nakładały się na siebie ani nie nakładały się na gesty.

Konfiguracja wstawki

Aby aplikacja miała pełną kontrolę nad tym, gdzie pobiera treści, wykonaj te czynności. Bez tych czynności aplikacja może rysować czarny lub jednolity kolor za interfejsem systemu oraz nie być animowana synchronicznie z klawiaturą programową.

  1. Zadzwoń pod numer enableEdgeToEdge() za Activity.onCreate. To wywołanie prosi o to, aby aplikacja była wyświetlana za interfejsem systemu. Aplikacja będzie mieć kontrolę nad tym, jak te wstawki są używane do dostosowywania interfejsu.
  2. Ustaw android:windowSoftInputMode="adjustResize" w sekcji AndroidManifest.xml aktywności. Dzięki temu ustawieniu rozmiar edytora IME aplikacji może być pobierany przez aplikację w postaci wstawki, dzięki czemu można odpowiednio rozmieszczać i rozmieszczać treści w razie pojawienia się i zniknięcia edytora.

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

Gdy aktywność przejmie kontrolę nad obsługą wszystkich wstawionych elementów, możesz użyć interfejsów API tworzenia, aby upewnić się, ż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 z uwzględnieniem zmian wprowadzonych.

To jest na przykład najbardziej podstawowa metoda stosowania wstawionych 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 dopełnienie wokół całej zawartości aplikacji. Dzięki temu interaktywne elementy nie nakładają się na interfejs systemu, ale oznacza to również, że żadna aplikacja nie będzie się wyświetlać za interfejsem systemu, aby uzyskać efekt od krawędzi do krawędzi. Aby w pełni wykorzystać całe okno, musisz dostroić, gdzie mają być stosowane wstawki, ekran po ekranie lub komponent po kolei.

Wszystkie te typy wstawionych elementów są automatycznie animowane za pomocą animacji IME zwracanych do interfejsu API 21. Dodatkowo wszystkie układy, w których są używane te wstawienia, są automatycznie animowane, gdy zmieniają się ich wartości.

Istnieją 2 główne sposoby korzystania z tych rodzajów wstawki do dostosowywania układów funkcji kompozycyjnej: modyfikatory dopełnienia i modyfikatory rozmiaru wstawionego.

Modyfikatory dopełnienia

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

Istnieje również kilka wbudowanych metod narzędziowych dla najpopularniejszych typów wstawki. Jedną z takich metod jest Modifier.safeDrawingPadding(), która odpowiada metodzie Modifier.windowInsetsPadding(WindowInsets.safeDrawing). W przypadku pozostałych typów wstawki są analogiczne modyfikatory.

Modyfikatory rozmiaru

Te modyfikatory stosują liczbę wstawionych okien, ustawiając rozmiar komponentu tak, aby odpowiadał rozmiarowi wstawki:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Stosuje stronę początkową Insets dla właściwości window jako szerokość (np. Modifier.width)

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Stosuje końcową stronę wsunięć okna jako szerokość (np. Modifier.width)

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

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

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Stosuje dolną stronę Insets jako wysokość (np. Modifier.height)

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

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

Wbudowane wykorzystanie

Modyfikatory dopełnienia (windowInsetsPadding i elementy pomocnicze, np. safeDrawingPadding) automatycznie przetwarzają tę część, która jest stosowana jako dopełnienie. Dokładniej analizując drzewo kompozycji, zagnieżdżone modyfikatory dopełnienia i modyfikatory rozmiaru wstawiają wiedzą, że część tych modyfikacji została już wykorzystana przez zewnętrzne modyfikatory dopełnienia. Unikaj też wielokrotnego używania tej samej części, co spowodowałoby znaczne dodatkowe miejsce.

Modyfikatory rozmiaru wstawionego elementu unikają też używania tej samej części wstawionych więcej niż raz, jeśli zostały już wykorzystane. Zmieniają jednak rozmiar bezpośrednio, dlatego nie korzystają z wbudowanych elementów.

W rezultacie modyfikatory zagnieżdżania automatycznie zmieniają ilość dopełnienia stosowanego do poszczególnych funkcji kompozycyjnych.

W tym samym przykładzie elementu LazyColumn co wcześniej widać, że element LazyColumn jest zmieniany przez modyfikator imePadding. Rozmiar ostatniego elementu w elemencie LazyColumn odpowiada wysokości u dołu pasków systemowych:

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

Gdy edytor IME jest zamknięty, modyfikator imePadding() nie stosuje dopełnienia, ponieważ edytor ten nie ma wysokości. Jako że modyfikator imePadding() nie stosuje dopełnienia, nie są przetwarzane żadne wstawki, a wysokość elementu Spacer będzie równa wysokości dolnej części pasków systemowych.

Po otwarciu edytora IME wstawki IME są animowane zgodnie z rozmiarem edytora, a modyfikator imePadding() zaczyna stosować dolne dopełnienie, aby zmienić rozmiar elementu LazyColumn podczas otwierania edytora. Gdy modyfikator imePadding() zaczyna stosować dolne dopełnienie, zaczyna też korzystać z takiej liczby. W związku z tym wysokość elementu Spacer zaczyna się zmniejszać, ponieważ odstępy między słupkami systemowymi zostały już zastosowane przez modyfikator imePadding(). Gdy modyfikator imePadding() zastosuje dolne dopełnienie większe niż paski systemowe, wysokość elementu Spacer wynosi zero.

Po zamknięciu edytora zmiany zachodzą odwrotnie: Spacer zaczyna rozwijać się od wysokości 0, gdy element imePadding() będzie stosować mniejszą wartość niż dolna część pasków systemowych, aż w końcu wartość Spacer będzie odpowiadać wysokości dolnej części pasków systemowych (gdy edytor IME zostanie całkowicie animowany).

Rysunek 2. Leniwa kolumna od krawędzi do krawędzi z wartością TextField

Jest to możliwe dzięki komunikacji między wszystkimi modyfikatorami windowInsetsPadding. Można na nie wpłynąć na kilka innych sposobów.

Modifier.consumeWindowInsets(insets: WindowInsets) również używa dopełnienia w taki sam sposób jak Modifier.windowInsetsPadding, ale nie stosuje zużytego elementu jako dopełnienia. Jest to przydatne w połączeniu z modyfikatorami rozmiaru w celu wskazania rodzeństwa, że określona liczba elementów 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 używa dowolnego argumentu PaddingValues. Jest to przydatne, gdy chcesz poinformować dzieci, że dopełnienie lub odstępy są wprowadzane za pomocą innego mechanizmu niż modyfikatory dopełnienia, np. zwykłe Modifier.padding lub stałe odstępy wysokości:

@OptIn(ExperimentalLayoutApi::class)
Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

W przypadkach, gdy potrzebne są nieprzetworzone wstawki okien, użyj bezpośrednio wartości WindowInsets lub użyj WindowInsets.asPaddingValues(), aby zwrócić PaddingValues z wbudowań, na które użycie nie ma wpływu. Jednak ze względu na ograniczenia opisane poniżej lepiej w miarę możliwości używać modyfikatorów dopełnienia okien i modyfikatorów rozmiaru w oknie.

Etapy Insets i Jetpack Compose

Funkcja Compose wykorzystuje podstawowe interfejsy API AndroidX do aktualizowania i animowania wstawienia, które wykorzystują bazowe interfejsy API platformy do zarządzania wstawieniami. Ze względu na to działanie platformy wstawki są ściśle powiązane z fazami Jetpack Compose.

Wartości wstawienia są aktualizowane po fazie kompozycji, ale przed etapem układu. Oznacza to, że odczyt wartości wstawienia w kompozycji zwykle używa tej wartości, która jest opóźniona o jedną klatkę. Wbudowane modyfikatory opisane na tej stronie opóźniają użycie wartości wstawionych do fazy układu, co zapewnia używanie tych wartości w tej samej ramce po ich aktualizacji.

Animacje IME klawiatury z funkcją WindowInsets

Możesz zastosować Modifier.imeNestedScroll() do przewijanego kontenera, aby automatycznie otwierać i zamykać edytor IME podczas przewijania na dół kontenera.

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 przewijający się w górę i w dół w celu zrobienia miejsca dla klawiatury.

Rysunek 1. Animacje IME

Wbudowana obsługa komponentów Material 3

Dla ułatwienia wiele wbudowanych uchwytów kompozycyjnych Material 3 (androidx.compose.material3) zawiera same wstawki w zależności od sposobu rozmieszczenia elementów kompozycyjnych w aplikacji zgodnie ze specyfikacją Material.

Obsługa wstawionych elementów kompozycyjnych

Poniżej znajdziesz listę komponentów Material Design, które automatycznie obsługują wstawki.

Paski aplikacji

Kontenery treści

Rusztowanie

Domyślnie Scaffold udostępnia wstawki jako parametr paddingValues, które można wykorzystać. Scaffold nie stosuje do treści elementów, za które odpowiadasz. Aby na przykład wykorzystać te wstawki z LazyColumn w 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 ustawienia domyślne

Aby skonfigurować działanie elementu kompozycyjnego, możesz zmienić parametr windowInsets przekazywany do funkcji kompozycyjnej. Ten parametr może mieć inny rodzaj ustawienia okna lub wyłączyć go, przekazując pustą instancję: WindowInsets(0, 0, 0, 0).

Aby na przykład wyłączyć obsługę wstawiania w elemencie LargeTopAppBar, ustaw parametr windowInsets na pustą instancję:

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

Interakcja z elementami wstawieniami systemowymi

Zastąpienie ustawień domyślnych może być konieczne, gdy ekran zawiera tę samą hierarchię widoków danych i kod tworzenia. W takim przypadku musisz wyraźnie wskazać, który z nich ma korzystać z wstawek, a który ignorować.

Jeśli na przykład najbardziej zewnętrznym układem jest układ Android View, musisz wykorzystać wstawione elementy z systemu Widok i zignorować je przy tworzeniu wiadomości. Jeśli najbardziej zewnętrzny układ to funkcja kompozycyjna, musisz wykorzystać wstawki w komponencie i odpowiednio dopełnić funkcje kompozycyjne AndroidView.

Domyślnie każdy element ComposeView wykorzystuje wszystkie wstawki na poziomie wykorzystania WindowInsetsCompat. Aby zmienić to domyślne działanie, ustaw parametr ComposeView.consumeWindowInsets na false.

Zasoby

  • Now in Android – w pełni funkcjonalna aplikacja na Androida opracowana wyłącznie przy użyciu technologii Kotlin i Jetpack Compose.