Wyświetlanie treści od krawędzi do krawędzi

Wypróbuj Compose
Jetpack Compose to zalecany zestaw narzędzi do tworzenia interfejsu na Androida. Dowiedz się, jak pracować z trybem od krawędzi do krawędzi w Compose.

Gdy kierujesz aplikację na pakiet SDK 35 lub nowszy na urządzeniu z Androidem 15 lub nowszym, aplikacja jest wyświetlana bez ramki. Okno obejmuje całą szerokość i wysokość wyświetlacza, rysując za paskami systemu. Paski systemu obejmują pasek stanu, pasek podpisów i pasek nawigacyjny.

Wiele aplikacji ma górny pasek aplikacji. Górny pasek aplikacji powinien rozciągać się do górnej krawędzi ekranu i wyświetlać się za paskiem stanu. Opcjonalnie górny pasek aplikacji może się zmniejszyć do wysokości paska stanu podczas przewijania treści.

Wiele aplikacji ma też dolny pasek aplikacji lub dolny pasek nawigacyjny. Te paski powinny też rozciągać się do dolnej krawędzi ekranu i wyświetlać za paskiem nawigacyjnym. W przeciwnym razie aplikacje powinny wyświetlać przewijane treści za paskiem nawigacyjnym.

Rysunek 1. Paski systemu w układzie od krawędzi do krawędzi.

Podczas wdrażania w aplikacji układu od krawędzi do krawędzi pamiętaj o tych kwestiach:

  1. Włączanie wyświetlania bez ramki
  2. Wdróż układy adaptacyjne, aby zoptymalizować wrażenia użytkowników na różnych formatach.
  3. Zarządzanie nakładaniem się elementów wizualnych
  4. Rozważ wyświetlanie scrimów za paskami systemu
przykład obrazu za paskiem stanu
Rysunek 2. Przykład obrazu za paskiem stanu.

Włącz wyświetlanie bez ramki

Jeśli aplikacja jest kierowana na pakiet SDK 35 lub nowszy, tryb od krawędzi do krawędzi jest automatycznie włączany na urządzeniach z Androidem 15 lub nowszym.

Aby włączyć wyświetlanie bez ramki w starszych wersjach Androida, ręcznie wywołaj enableEdgeToEdge w onCreate w Activity.

Kotlin

 override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         WindowCompat.enableEdgeToEdge(window)
        ...
      }

Java

 @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        WindowCompat.enableEdgeToEdge(getWindow());
        ...
      }

Domyślnie enableEdgeToEdge() sprawia, że paski systemu są przezroczyste, z wyjątkiem trybu nawigacji z 3 przyciskami, w którym pasek nawigacyjny jest półprzezroczysty. Kolory ikon systemowych i zasłony są dostosowywane na podstawie jasnego lub ciemnego motywu systemu.

Aby włączyć wyświetlanie bez ramki w aplikacji bez używania funkcji enableEdgeToEdge(), zapoznaj się z artykułem Ręczne konfigurowanie wyświetlania bez ramki.

Obsługa nakładania się za pomocą wcięć

Niektóre widoki aplikacji mogą być rysowane za paskami systemu, jak pokazano na rysunku 3.

Możesz rozwiązać problem nakładania się, reagując na odcięcia, które określają, które części ekranu przecinają się z interfejsem systemu, np. z paskiem nawigacyjnym lub paskiem stanu. Przecinanie się może oznaczać wyświetlanie się nad treścią, ale może też informować aplikację o gestach systemowych.

Typy wcięć, które mają zastosowanie do wyświetlania aplikacji od krawędzi do krawędzi:

  • Wstawki pasków systemu: najlepiej sprawdzają się w przypadku widoków, które można kliknąć i które nie mogą być wizualnie zasłonięte przez paski systemu.

  • Wstawki wycięcia w ekranie: w obszarach, w których może występować wycięcie w ekranie ze względu na kształt urządzenia.

  • Wstawki gestów systemowych: w przypadku obszarów nawigacji przy użyciu gestów używanych przez system, które mają pierwszeństwo przed aplikacją.

Wstawki pasków systemu

Wstawki paska systemowego to najczęściej używany typ wstawek. Reprezentują one obszar, w którym interfejs systemu jest wyświetlany na osi Z nad aplikacją. Najlepiej używać ich do przenoszenia lub wypełniania widoków w aplikacji, które można klikać i które nie mogą być wizualnie zasłonięte przez paski systemowe.

Na przykład pływający przycisk polecenia (FAB) na rysunku 3 jest częściowo zasłonięty przez pasek nawigacyjny:

Przykład implementacji od krawędzi do krawędzi, ale pasek nawigacyjny zasłania pływający przycisk akcji
Rysunek 3. Pasek nawigacyjny nakładający się na pływający przycisk akcji w układzie od krawędzi do krawędzi.

Aby uniknąć tego rodzaju nakładania się elementów wizualnych w trybie gestów lub przycisków, możesz zwiększyć marginesy widoku, używając getInsets(int) z WindowInsetsCompat.Type.systemBars().

Poniższy przykładowy kod pokazuje, jak zaimplementować wstawki paska systemowego:

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(fab) { v, windowInsets ->
  val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
  // Apply the insets as a margin to the view. This solution sets
  // only the bottom, left, and right dimensions, but you can apply whichever
  // insets are appropriate to your layout. You can also update the view padding
  // if that's more appropriate.
  v.updateLayoutParams<MarginLayoutParams> {
      leftMargin = insets.left
      bottomMargin = insets.bottom
      rightMargin = insets.right
  }

  // Return CONSUMED if you don't want the window insets to keep passing
  // down to descendant views.
  WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(fab, (v, windowInsets) -> {
  Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
  // Apply the insets as a margin to the view. This solution sets only the
  // bottom, left, and right dimensions, but you can apply whichever insets are
  // appropriate to your layout. You can also update the view padding if that's
  // more appropriate.
  MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
  mlp.leftMargin = insets.left;
  mlp.bottomMargin = insets.bottom;
  mlp.rightMargin = insets.right;
  v.setLayoutParams(mlp);

  // Return CONSUMED if you don't want the window insets to keep passing
  // down to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

Jeśli zastosujesz to rozwiązanie w przykładzie pokazanym na rysunku 3, w trybie przycisku nie będzie nakładania się elementów, jak pokazano na rysunku 4:

półprzezroczysty pasek nawigacyjny, który nie zasłania pływającego przycisku akcji;
Rysunek 4. Rozwiązywanie problemu z nakładaniem się elementów wizualnych w trybie przycisków.

To samo dotyczy trybu nawigacji przy użyciu gestów, jak pokazano na rysunku 5:

wyświetlanie od krawędzi do krawędzi z nawigacją przy użyciu gestów
Rysunek 5. Rozwiązywanie problemu z nakładaniem się elementów wizualnych w trybie nawigacji przy użyciu gestów.

Wstawki wycięcia w ekranie

Niektóre urządzenia mają wycięcia w ekranie. Zwykle wycięcie znajduje się u góry ekranu i jest uwzględnione na pasku stanu. Gdy ekran urządzenia jest w trybie poziomym, wycięcie może znajdować się na krawędzi pionowej. W zależności od treści, które aplikacja wyświetla na ekranie, należy zastosować dopełnienie, aby uniknąć wycięć w ekranie, ponieważ domyślnie aplikacje będą wyświetlane w wycięciu w ekranie.

Na przykład wiele ekranów aplikacji wyświetla listę elementów. Nie zasłaniaj elementów listy wycięciem w ekranie ani paskami systemu.

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(binding.recyclerView) { v, insets ->
  val bars = insets.getInsets(
    WindowInsetsCompat.Type.systemBars()
      or WindowInsetsCompat.Type.displayCutout()
  )
  v.updatePadding(
    left = bars.left,
    top = bars.top,
    right = bars.right,
    bottom = bars.bottom,
  )
  WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(mBinding.recyclerView, (v, insets) -> {
  Insets bars = insets.getInsets(
    WindowInsetsCompat.Type.systemBars()
    | WindowInsetsCompat.Type.displayCutout()
  );
  v.setPadding(bars.left, bars.top, bars.right, bars.bottom);
  return WindowInsetsCompat.CONSUMED;
});

Określ wartość WindowInsetsCompat, stosując logiczne OR do pasków systemu i typów wycięć w ekranie.

Ustaw clipToPadding na RecyclerView, aby dopełnienie przewijało się wraz z elementami listy. Dzięki temu elementy mogą się przesuwać za paski systemowe, gdy użytkownik przewija ekran, jak w tym przykładzie.

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

Wstawki gestów systemowych

Odcięcia gestów systemowych to obszary okna, w których gesty systemowe mają pierwszeństwo przed aplikacją. Te obszary są oznaczone na rysunku 6 na pomarańczowo:

Przykład wstawek gestów systemowych
Rysunek 6. Wstawki gestów systemowych.

Podobnie jak w przypadku dopełnień pasków systemowych, możesz uniknąć nakładania się dopełnień gestów systemowych, używając getInsets(int) z WindowInsetsCompat.Type.systemGestures().

Użyj tych wstawek, aby odsunąć widoki z możliwością przesuwania od krawędzi. Typowe przypadki użycia to plansze dolne, przesuwanie w grach i karuzele zaimplementowane za pomocą ViewPager2.

Na Androidzie 10 lub nowszym odcięcia gestów systemowych zawierają dolne odcięcie dla gestu ekranu głównego oraz lewe i prawe odcięcie dla gestów wstecz:

przykład pomiarów wstawki gestu systemowego
Rysunek 7. Wymiary wstawki gestu systemowego.

Poniższy przykładowy kod pokazuje, jak zaimplementować wstawki gestów systemowych:

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
    val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
    // Apply the insets as padding to the view. Here, set all the dimensions
    // as appropriate to your layout. You can also update the view's margin if
    // more appropriate.
    view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)

    // Return CONSUMED if you don't want the window insets to keep passing down
    // to descendant views.
    WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
    Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
    // Apply the insets as padding to the view. Here, set all the dimensions
    // as appropriate to your layout. You can also update the view's margin if
    // more appropriate.
    view.setPadding(insets.left, insets.top, insets.right, insets.bottom);

    // Return CONSUMED if you don't want the window insets to keep passing down
    // to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

Komponenty Material Design

Wiele opartych na widokach komponentów Android Material (com.google.android.material) automatycznie obsługuje wstawki, w tym BottomAppBar, BottomNavigationView, NavigationRailView i NavigationView.

Jednak AppBarLayout nie obsługuje automatycznie wcięć. Dodaj android:fitsSystemWindows="true", aby obsłużyć górne wcięcia.

Dowiedz się, jak obsługiwać elementy wewnętrzne za pomocą komponentów Material Design w Compose.

Zgodne wstecznie wysyłanie wstawki

Aby zatrzymać wysyłanie dopełnień do widoków podrzędnych i uniknąć nadmiernego dopełnienia, możesz wykorzystać dopełnienia za pomocą stałej WindowInsetsCompat.CONSUMED. Jednak na urządzeniach z Androidem 10 (poziom interfejsu API 29 i starsze) po wywołaniu WindowInsetsCompat.CONSUMED wstawki nie są wysyłane do elementów równorzędnych, co może powodować niezamierzone nakładanie się elementów wizualnych.

Przykład nieprawidłowego wysyłania wstawki
Rysunek 8. Przykład nieprawidłowego wysyłania wstawki. Wstawki nie są wysyłane do widoków równorzędnych po tym, jak ViewGroup 1 wykorzystuje wstawki na Androidzie 10 (poziom API 29) i starszych, co powoduje, że TextView 2 nakłada się na pasek nawigacyjny systemu. Jednak w Androidzie 11 (poziom API 30) i nowszych wersjach elementy wewnętrzne są wysyłane do widoków nadrzędnych zgodnie z oczekiwaniami.

Aby mieć pewność, że elementy wewnętrzne są wysyłane do elementów równorzędnych we wszystkich obsługiwanych wersjach Androida, przed wykorzystaniem elementów wewnętrznych użyj ViewGroupCompat#installCompatInsetsDispatch. Jest to dostępne w AndroidX Core i Core-ktx 1.16.0-alpha01 i nowszych wersjach.

Kotlin

// Use the i.d. assigned to your layout's root view, e.g. R.id.main
val rootView = findViewById(R.id.main)
// Call before consuming insets
ViewGroupCompat.installCompatInsetsDispatch(rootView)

Java

// Use the i.d. assigned to your layout's root view, e.g. R.id.main
LinearLayout rootView = findViewById(R.id.main);
// Call before consuming insets
ViewGroupCompat.installCompatInsetsDispatch(rootView);
Przykład wysyłania stałych wstawek
Rysunek 9. Naprawiliśmy problem z wysyłaniem wstawki po wywołaniu ViewGroupCompat#installCompatInsetsDispatch.

Tryb pojemny

Niektóre treści najlepiej ogląda się na pełnym ekranie, co zapewnia użytkownikom bardziej wciągające wrażenia. Paski systemu w trybie pojemnym możesz ukryć za pomocą bibliotek WindowInsetsController i WindowInsetsControllerCompat:

Kotlin

val windowInsetsController =
      WindowCompat.getInsetsController(window, window.decorView)

// Hide the system bars.
windowInsetsController.hide(Type.systemBars())

// Show the system bars.
windowInsetsController.show(Type.systemBars())

Java

Window window = getWindow();
WindowInsetsControllerCompat windowInsetsController =
      WindowCompat.getInsetsController(window, window.getDecorView());
if (windowInsetsController == null) {
    return;
  }
// Hide the system bars.
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());

// Show the system bars.
windowInsetsController.show(WindowInsetsCompat.Type.systemBars());

Więcej informacji o implementacji tej funkcji znajdziesz w sekcji Ukrywanie pasków systemu w trybie pojemnym.

Ikony na pasku systemu

Wywołanie enableEdgeToEdge zapewnia aktualizację kolorów ikon na pasku systemowym po zmianie motywu urządzenia.

Podczas przechodzenia na tryb od krawędzi do krawędzi może być konieczne ręczne zaktualizowanie kolorów ikon paska systemu, aby kontrastowały z tłem aplikacji. Aby na przykład utworzyć jasne ikony na pasku stanu:

Kotlin

WindowCompat.getInsetsController(window, window.decorView)
    .isAppearanceLightStatusBars = false

Java

WindowCompat.getInsetsController(window, window.getDecorView())
    .setAppearanceLightStatusBars(false);

Ochrona paska systemu

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

Ustawienia domyślne systemu mogą jednak nie działać we wszystkich przypadkach użycia. Zapoznaj się ze wskazówkami dotyczącymi projektowania pasków systemu Android i projektowania aplikacji bez ramki, aby określić, czy używać przezroczystych czy półprzezroczystych pasków systemu.

Tworzenie przezroczystych pasków systemowych

Utwórz przezroczysty pasek stanu, kierując aplikację na Androida 15 (SDK 35) lub nowszego albo wywołując enableEdgeToEdge() z domyślnymi argumentami w przypadku wcześniejszych wersji.

Utwórz przezroczysty pasek nawigacji przy użyciu gestów, kierując aplikację na Androida 15 lub nowszego albo wywołując funkcję enableEdgeToEdge() z domyślnymi argumentami w przypadku wcześniejszych wersji. W przypadku paska nawigacji z 3 przyciskami ustaw Window.setNavigationBarContrastEnforced na false, w przeciwnym razie zostanie zastosowana półprzezroczysta nakładka.

Tworzenie półprzezroczystych pasków systemowych

Aby utworzyć półprzezroczysty pasek stanu, wykonaj te czynności:

  1. Zaktualizuj zależność androidx-core do wersji 1.16.0-beta01 lub nowszej.
  2. Umieść układ XML w elemencie androidx.core.view.insets.ProtectionLayout i przypisz identyfikator.
  3. Uzyskaj dostęp do ProtectionLayout za pomocą kodu, aby ustawić zabezpieczenia, określając stronę i GradientProtection paska stanu.

<androidx.core.view.insets.ProtectionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/list_protection"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ScrollView
        android:id="@+id/item_list"
        android:clipToPadding="false"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!--items-->

    </ScrollView>

</androidx.core.view.insets.ProtectionLayout>

findViewById<ProtectionLayout>(R.id.list_protection)
    .setProtections(
        listOf(
            GradientProtection(
                WindowInsetsCompat.Side.TOP,
                // Ideally, this is the pane's background color
                paneBackgroundColor
            )
        )
    )

Upewnij się, że wartość ColorInt przekazywana do GradientProtection pasuje do tła treści. Na przykład układ szczegółowej listy wyświetlany na urządzeniu składanym może mieć różne GradientProtections w różnych kolorach dla panelu listy i panelu szczegółów.

Rysunek 1. Gradientowa ochrona w różnych kolorach.

Nie twórz półprzezroczystego paska nawigacji przy użyciu gestów. Aby utworzyć półprzezroczysty pasek nawigacyjny z 3 przyciskami, wykonaj jedną z tych czynności:

  • Jeśli układ jest już opakowany w element ProtectionView, możesz przekazać dodatkowy element ColorProtection lub GradientProtection do metody setProtections. Zanim to zrobisz, upewnij się, że window.isNavigationBarContrastEnforced = false.
  • W przeciwnym razie ustaw window.isNavigationBarContrastEnforced = true.

Inne wskazówki

Dodatkowe wskazówki dotyczące obsługi wcięć.

Wyświetlanie przewijanych treści na całej powierzchni ekranu

Sprawdź, czy ostatni element listy nie jest zasłonięty przez paski systemowe w RecyclerView lub NestedScrollView, obsługując wstawki i ustawiając clipToPadding na false.

Poniższy film przedstawia aplikację RecyclerView z wyłączonym (po lewej) i włączonym (po prawej) wyświetlaniem od krawędzi do krawędzi:

Przykładowy kod znajdziesz we fragmentach kodu w sekcji Tworzenie list dynamicznych za pomocą RecyclerView.

Wyświetlanie okien pełnoekranowych bez ramki

Aby okno dialogowe wyświetlało się na pełnym ekranie, wywołaj enableEdgeToEdge w obiekcie Dialog.

Kotlin

class MyAlertDialogFragment : DialogFragment() {
    override fun onStart(){
        super.onStart()
        dialog?.window?.let { WindowCompat.enableEdgeToEdge(it) }
    }
    ...
}

Java

public class MyAlertDialogFragment extends DialogFragment {
    @Override
    public void onStart() {
        super.onStart();
        Dialog dialog = getDialog();
        if (dialog != null) {
            Window window = dialog.getWindow();
            if (window != null) {
                WindowCompat.enableEdgeToEdge(window);
            }
        }
    }
    ...
}

Dodatkowe materiały

Więcej informacji o wyświetlaniu od krawędzi do krawędzi znajdziesz w tych materiałach.

Blogi

Design

Inna dokumentacja

Filmy