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

Wypróbuj Compose
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak pracować z wyświetlaniem 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 wyświetla się bez ramki. Okno zajmuje całą szerokość i wysokość wyświetlacza, ponieważ jest rysowane za paskami systemowymi. Paski systemowe to pasek stanu, pasek tytułu i pasek nawigacji.

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. Paski te powinny też rozciągać się do dolnej krawędzi ekranu i wyświetlać się za paskiem nawigacyjnym. W przeciwnym razie aplikacje powinny wyświetlać przewijane treści za paskiem nawigacyjnym.

Rysunek 1. Paski systemowe 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łącz wyświetlanie bez ramki
  2. Rozwiąż problemy z nakładaniem się elementów wizualnych.
  3. Rozważ umieszczenie za belkami systemu ekranów.
przykład obrazu za paskiem stanu;
Rysunek 2. Przykład obrazu za paskiem stanu.

Włącz wyświetlanie bez ramki

Jeśli Twoja aplikacja jest kierowana na SDK 35 lub nowszy, wyświetlanie bez ramki jest automatycznie włączane na urządzeniach z Androidem 15 lub nowszym.

Aby włączyć wyświetlanie od krawędzi do krawędzi w starszych wersjach Androida, ręcznie wywołaj enableEdgeToEdgeonCreate swojego 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 systemowe są przezroczyste, z wyjątkiem trybu nawigacji 3-przyciskowej, w którym pasek stanu jest półprzezroczysty. Kolory ikon systemowych i półprzezroczystej nakładki są dostosowywane do jasnego lub ciemnego motywu systemu.

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

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

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

Możesz rozwiązać problem z nakładaniem się elementów, reagując na wcięcia, które określają, które części ekranu przecinają się z interfejsem systemu, takim jak pasek nawigacyjny lub pasek stanu. Nakładanie się może oznaczać wyświetlanie nad treścią, ale może też informować aplikację o gestach systemowych.

Typy wcięć, które mają zastosowanie do wyświetlania aplikacji bez ramki:

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

  • Wcięcia w wycięciu ekranu: w przypadku obszarów, w których może występować wycięcie ekranu ze względu na kształt urządzenia.

  • Wstawki gestów systemowych: obszary nawigacji gestami używane przez system, które mają wyższy priorytet niż Twoja aplikacja.

Wstawki paska systemowego

Wstawki paska systemowego są najczęściej używanym typem wstawek. Reprezentują one obszar, w którym interfejs systemu wyświetla się na osi Z nad aplikacją. Najlepiej używać ich do przesuwania lub wypełniania widoków w aplikacji, które można kliknąć i które nie mogą być wizualnie zasłonięte przez paski systemowe.

Na przykład pływający przycisk działania (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 przycisk FAB;
Rysunek 3. Pasek nawigacyjny nakładający się na pływający przycisk działania w układzie od krawędzi do krawędzi.

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

Poniższy przykład kodu 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 żadnego nakładania się elementów wizualnych, jak pokazano na rysunku 4:

przezroczysty pasek nawigacyjny, który nie zasłania przycisku FAB;
Rysunek 4. Rozwiązanie problemu z nakładaniem się elementów wizualnych w trybie przycisku.

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

bez ramki z nawigacją przy użyciu gestów,
Rysunek 5. Rozwiązanie 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 na wyświetlaczu. Zwykle wycięcie znajduje się u góry ekranu i jest częścią paska stanu. Gdy ekran urządzenia jest w trybie poziomym, wycięcie może znajdować się na pionowej krawędzi. W zależności od treści wyświetlanych przez aplikację na ekranie należy zastosować dopełnienie, aby uniknąć wycięć na wyświetlaczu, ponieważ domyślnie aplikacje będą rysować w wycięciu na wyświetlaczu.

Na przykład na wielu ekranach aplikacji wyświetlana jest lista elementów. Nie zasłaniaj elementów listy wycięciem na wyświetlaczu ani paskami systemowymi.

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, wykonując logiczną operację OR na paskach systemowych i typach wycięcia na wyświetlaczu.

Ustaw wartość clipToPadding na RecyclerView, aby dopełnienie przewijało się wraz z elementami listy. Dzięki temu elementy mogą znajdować się za paskami systemowymi, gdy użytkownik przewija stronę, jak pokazano w przykładzie poniżej.

<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

Wstawki gestów systemowych to obszary okna, w których gesty systemowe mają wyższy priorytet niż aplikacja. Obszary te są oznaczone na pomarańczowo na rysunku 6:

Przykład wstawień gestów systemowych
Rysunek 6. Wstawki gestów systemowych.

Podobnie jak w przypadku wcięć paska systemowego możesz uniknąć nakładania się wcięć gestów systemowych, używając getInsets(int)WindowInsetsCompat.Type.systemGestures().

Użyj tych wcięć, aby przesunąć widoki, które można przesuwać, lub dodać do nich dopełnienie, tak aby nie znajdowały się przy krawędziach. Typowe przypadki użycia to arkusze u dołu ekranu, przesuwanie w grach i karuzele zaimplementowane za pomocą ViewPager2.

W Androidzie 10 lub nowszym odcięcia gestów systemowych obejmują odcięcie dolne dla gestu głównego oraz odcięcia po lewej i prawej stronie dla gestów wstecz:

przykład pomiarów wcięcia gestu systemowego
Rysunek 7. Wymiary wcięcia gestu systemowego.

Poniższy przykład kodu 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

Wiele komponentów Android Material Components (com.google.android.material) opartych na widokach automatycznie obsługuje wcięcia, w tym BottomAppBar, BottomNavigationView, NavigationRailViewNavigationView.

Jednak AppBarLayout nie obsługuje automatycznie wcięć. Dodaj android:fitsSystemWindows="true" do obsługi górnych wcięć.

Dowiedz się, jak obsługiwać wcięcia za pomocą komponentów Material w Compose.

Wysyłanie wstawek zgodnych wstecznie

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

Przykład nieprawidłowego wysyłania wstawki
Rysunek 8. Przykład nieprawidłowego wysyłania wstawki. W systemie Android 10 (poziom API 29) i starszych element ViewGroup 1 po wykorzystaniu wstawień nie przekazuje ich do widoków równorzędnych, co powoduje, że element TextView 2 nakłada się na systemowy pasek nawigacyjny. Jednak wstawki są wysyłane do widoków równorzędnych na Androidzie 11 (poziom API 30) i nowszym, zgodnie z oczekiwaniami.

Aby potwierdzić, że elementy wstawki są wysyłane do elementów równorzędnych we wszystkich obsługiwanych wersjach Androida, użyj funkcji ViewGroupCompat#installCompatInsetsDispatch before consuming insets, dostępnej w AndroidX Core i Core-ktx w wersji 1.16.0-alpha01 lub nowszej.

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 z ustalonym wcięciem
Rysunek 9. Rozwiązaliśmy problem z wysyłaniem wstawek po wywołaniu metody ViewGroupCompat#installCompatInsetsDispatch.

Tryb pojemny

Niektóre treści najlepiej wyświetlać na pełnym ekranie, co zapewnia użytkownikom większe zaangażowanie. Paski systemowe możesz ukryć w trybie pełnoekranowym za pomocą bibliotek WindowInsetsControllerWindowInsetsControllerCompat:

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 wdrażaniu tej funkcji znajdziesz w artykule Ukrywanie pasków systemowych w trybie pełnoekranowym.

Ikony na pasku systemowym

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

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

Kotlin

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

Java

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

Ochrona paska systemowego

Gdy aplikacja będzie kierowana na pakiet SDK w wersji 35 lub nowszej, wyświetlanie od krawędzi do krawędzi będzie wymuszane. Pasek stanu systemu i paski nawigacji przy użyciu gestów są przezroczyste, ale pasek nawigacji przy użyciu 3 przycisków jest półprzezroczysty. Zadzwoń pod numer enableEdgeToEdge, aby ustawić zgodność wsteczną.

Jednak domyślne ustawienia systemu mogą nie działać w przypadku wszystkich zastosowań. Zapoznaj się z wskazówkami dotyczącymi projektowania pasków systemowych na Androidzieprojektowaniem od krawędzi do krawędzi, aby określić, czy używać przezroczystych czy półprzezroczystych pasków systemowych.

Tworzenie przezroczystych pasków systemowych

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

Utwórz przezroczysty pasek nawigacyjny gestów, kierując reklamy na Androida 15 lub nowszego albo wywołując enableEdgeToEdge() z argumentami domyślnymi w przypadku starszych wersji. W przypadku 3-przyciskowego paska nawigacyjnego ustaw wartość Window.setNavigationBarContrastEnforced na false. W przeciwnym razie zostanie zastosowana półprzezroczysta zasłona.

Tworzenie półprzezroczystych pasków systemowych

Aby utworzyć półprzezroczysty pasek stanu:

  1. Zaktualizuj zależność androidx-core do wersji 1.16.0-beta01 lub nowszej.
  2. Owiń układ XML w tagi androidx.core.view.insets.ProtectionLayout i przypisz identyfikator.
  3. Programowo uzyskaj dostęp do ProtectionLayout, aby ustawić ochronę, określając stronę i GradientProtection dla 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
            )
        )
    )

Sprawdź, czy wartość parametru ColorInt przekazywana do parametru GradientProtection jest zgodna z tłem treści. Na przykład układ lista-szczegóły 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 nawigacyjnego opartego na gestach. Aby utworzyć półprzezroczysty pasek nawigacyjny z 3 przyciskami, wykonaj jedną z tych czynności:

  • Jeśli układ jest już zawarty w elemencie 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. Jeśli połączenia enableEdgeToEdge, window.isNavigationBarContrastEnforced = true w aplikacji są domyślne.

Inne wskazówki

Dodatkowe wskazówki dotyczące obsługi wstawek.

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

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 pokazuje RecyclerView z wyłączonym (po lewej) i włączonym (po prawej) wyświetlaczem od krawędzi do krawędzi:

Przykłady kodu znajdziesz w sekcji Tworzenie dynamicznych list za pomocą elementu RecyclerView.

Wyświetlanie okien pełnoekranowych od krawędzi do krawędzi

Aby okno pełnoekranowe zajmowało cały ekran, wywołaj enableEdgeToEdge w oknie.

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 treści od krawędzi do krawędzi znajdziesz w tych materiałach:

Blogi

Design

Inna dokumentacja

Filmy