Informuj o zwijaniu aplikacji

Duże, rozłożone wyświetlacze i unikalne stany złożenia umożliwiają korzystanie z nowych funkcji na urządzeniach składanych. Aby aplikacja była dostosowana do składania, użyj biblioteki Jetpack WindowManager, która udostępnia interfejs API do obsługi funkcji okien na urządzeniach składanych takich jak zagięcia i zawiasy. Gdy aplikacja jest dostosowana do składania, może dostosować swój układ, aby nie umieszczać ważnych treści w obszarze zagięć lub zawiasów, i używać zagięć oraz zawiasów jako naturalnych separatorów.

Informacje o tym, czy urządzenie obsługuje konfiguracje takie jak tryb stołu lub tryb książki, mogą pomóc w podejmowaniu decyzji o obsłudze różnych układów lub udostępnianiu określonych funkcji.

Informacje o oknie

Interfejs WindowInfoTracker w Jetpack WindowManager udostępnia informacje o układzie okna. Metoda windowLayoutInfo() tego interfejsu zwraca strumień danych WindowLayoutInfo, który informuje aplikację o stanie złożenia urządzenia składanego. Metoda WindowInfoTracker#getOrCreate() tworzy instancję WindowInfoTracker.

WindowManager obsługuje zbieranie danych WindowLayoutInfo za pomocą Kotlin flows i wywołań zwrotnych Java.

Kotlin flows

Aby rozpocząć i zatrzymać zbieranie danych WindowLayoutInfo, możesz użyć współprogramu z możliwością ponownego uruchomienia, który jest świadomy cyklu życia. Blok kodu repeatOnLifecycle jest wykonywany, gdy cykl życia jest co najmniej w stanie STARTED, i zatrzymywany, gdy cykl życia jest w stanie STOPPED. Wykonanie bloku kodu jest automatycznie wznawiane, gdy cykl życia ponownie osiągnie stan STARTED. W tym przykładzie blok kodu zbiera i wykorzystuje dane WindowLayoutInfo:

class DisplayFeaturesActivity : ComponentActivity() {

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

        lifecycleScope.launch(Dispatchers.Main) {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
                    .windowLayoutInfo(this@DisplayFeaturesActivity)
                    .collect { newLayoutInfo ->
                        // Use newLayoutInfo to update the layout.
                    }
            }
        }
    }
}

Wywołania zwrotne Java

Warstwa zgodności wywołań zwrotnych zawarta w zależności androidx.window:window-java umożliwia zbieranie WindowLayoutInfo aktualizacji bez używania Kotlin flow. Artefakt zawiera klasę WindowInfoTrackerCallbackAdapter, która dostosowuje WindowInfoTracker do obsługi rejestrowania (i wyrejestrowywania) wywołań zwrotnych w celu otrzymywania aktualizacji WindowLayoutInfo, na przykład:

public class SplitLayoutActivity extends AppCompatActivity {

    private WindowInfoTrackerCallbackAdapter windowInfoTracker;
    private ActivitySplitLayoutBinding binding;
    private final LayoutStateChangeCallback layoutStateChangeCallback =
            new LayoutStateChangeCallback();

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

       windowInfoTracker =
                new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
   }

   @Override
   protected void onStart() {
       super.onStart();
       windowInfoTracker.addWindowLayoutInfoListener(
                this, Runnable::run, layoutStateChangeCallback);
   }

   @Override
   protected void onStop() {
       super.onStop();
       windowInfoTracker
           .removeWindowLayoutInfoListener(layoutStateChangeCallback);
   }

   class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
       @Override
       public void accept(WindowLayoutInfo newLayoutInfo) {
           SplitLayoutActivity.this.runOnUiThread( () -> {
               // Use newLayoutInfo to update the layout.
           });
       }
   }
}

Obsługa RxJava

Jeśli używasz już RxJava (w wersji 2 lub 3), możesz skorzystać z artefaktów, które umożliwiają używanie Observable lub Flowable do zbierania aktualizacji WindowLayoutInfo bez używania Kotlin flow.

Warstwa zgodności udostępniana przez androidx.window:window-rxjava2 i androidx.window:window-rxjava3 zależności zawiera metody WindowInfoTracker#windowLayoutInfoFlowable() i WindowInfoTracker#windowLayoutInfoObservable(), które umożliwiają aplikacji otrzymywanie WindowLayoutInfo aktualizacji, na przykład:

class RxActivity: AppCompatActivity {

    private lateinit var binding: ActivityRxBinding

    private var disposable: Disposable? = null
    private lateinit var observable: Observable<WindowLayoutInfo>

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivityRxBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

        // Create a new observable.
        observable = WindowInfoTracker.getOrCreate(this@RxActivity)
            .windowLayoutInfoObservable(this@RxActivity)
   }

   @Override
   protected void onStart() {
       super.onStart();

        // Subscribe to receive WindowLayoutInfo updates.
        disposable?.dispose()
        disposable = observable
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { newLayoutInfo ->
            // Use newLayoutInfo to update the layout.
        }
   }

   @Override
   protected void onStop() {
       super.onStop();

        // Dispose of the WindowLayoutInfo observable.
        disposable?.dispose()
   }
}

Funkcje wyświetlaczy składanych

Klasa WindowLayoutInfo w Jetpack WindowManager udostępnia funkcje okna wyświetlacza jako listę elementów DisplayFeature.

FoldingFeature to typ DisplayFeature, który zawiera informacje o wyświetlaczach składanych, w tym te właściwości:

  • state: stan złożenia urządzenia, FLAT lub HALF_OPENED

  • orientation: orientacja zagięcia lub zawiasu, HORIZONTAL lub VERTICAL.

  • occlusionType: czy zagięcie lub zawias zasłania część wyświetlacza, NONE lub FULL.

  • isSeparating: czy zagięcie lub zawias tworzy 2 logiczne obszary wyświetlania, prawda lub fałsz.

Urządzenie składane, które jest HALF_OPENED, zawsze zgłasza isSeparating jako prawdę, ponieważ ekran jest podzielony na 2 obszary wyświetlania. Ponadto isSeparating jest zawsze prawdą na urządzeniu z 2 ekranami, gdy aplikacja obejmuje oba ekrany.

Właściwość FoldingFeature bounds (dziedziczona z DisplayFeature) reprezentuje prostokąt ograniczający funkcji składania, takiej jak zagięcie lub zawias. Granice można wykorzystać do pozycjonowania elementów na ekranie względem funkcji:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    lifecycleScope.launch(Dispatchers.Main) {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            // Safely collects from WindowInfoTracker when the lifecycle is
            // STARTED and stops collection when the lifecycle is STOPPED.
            WindowInfoTracker.getOrCreate(this@MainActivity)
                .windowLayoutInfo(this@MainActivity)
                .collect { layoutInfo ->
                    // New posture information.
                    val foldingFeature = layoutInfo.displayFeatures
                        .filterIsInstance<FoldingFeature>()
                        .firstOrNull()
                    // Use information from the foldingFeature object.
                }
        }
    }
}

Java

private WindowInfoTrackerCallbackAdapter windowInfoTracker;
private final LayoutStateChangeCallback layoutStateChangeCallback =
                new LayoutStateChangeCallback();

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    // ...
    windowInfoTracker =
            new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}

@Override
protected void onStart() {
    super.onStart();
    windowInfoTracker.addWindowLayoutInfoListener(
            this, Runnable::run, layoutStateChangeCallback);
}

@Override
protected void onStop() {
    super.onStop();
    windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}

class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
    @Override
    public void accept(WindowLayoutInfo newLayoutInfo) {
        // Use newLayoutInfo to update the Layout.
        List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures();
        for (DisplayFeature feature : displayFeatures) {
            if (feature instanceof FoldingFeature) {
                // Use information from the feature object.
            }
        }
    }
}

Tryb stołu

Korzystając z informacji zawartych w obiekcie FoldingFeature, aplikacja może obsługiwać pozycje takie jak tryb stołu, w którym telefon leży na powierzchni, zawias jest w pozycji poziomej, a ekran składany jest otwarty w połowie.

Tryb stołu umożliwia użytkownikom wygodne korzystanie z telefonów bez konieczności trzymania ich w dłoni. Tryb stołu świetnie sprawdza się podczas oglądania multimediów, robienia zdjęć i prowadzenia rozmów wideo.

Rysunek 1. Aplikacja odtwarzacza wideo w trybie stołu – film w pionowej części ekranu, elementy sterujące odtwarzaniem w części poziomej.

Aby sprawdzić czy urządzenie jest w trybie stołu, użyj FoldingFeature.State i FoldingFeature.Orientation:

Kotlin

fun isTableTopPosture(foldFeature: FoldingFeature?): Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
        foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
}

Java

boolean isTableTopPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL);
}

Gdy wiesz, że urządzenie jest w trybie stołu, odpowiednio zaktualizuj układ aplikacji. W przypadku aplikacji multimedialnych oznacza to zwykle umieszczenie odtwarzania w części strony widocznej na ekranie oraz umieszczenie elementów sterujących i treści dodatkowych tuż za nim, aby zapewnić wygodne oglądanie lub słuchanie bez użycia rąk.

W Androidzie 15 (poziom interfejsu API 35) i nowszym możesz wywołać synchroniczny interfejs API, aby wykryć, czy urządzenie obsługuje tryb stołu, niezależnie od jego bieżącego stanu.

Interfejs API udostępnia listę pozycji obsługiwanych przez urządzenie. Jeśli lista zawiera tryb stołu, możesz podzielić układ aplikacji, aby obsługiwać tę pozycję, i przeprowadzić testy A/B interfejsu aplikacji w układach trybu stołu i pełnoekranowych.

Kotlin

if (WindowSdkExtensions.getInstance().extensionVersion >= 6) {
    val postures = WindowInfoTracker.getOrCreate(context).supportedPostures
    if (postures.contains(SupportedPosture.TABLETOP)) {
        // Device supports tabletop posture.
    }
}

Java

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures();
    if (postures.contains(SupportedPosture.TABLETOP)) {
        // Device supports tabletop posture.
    }
}

Przykłady

Tryb książki

Kolejną unikalną funkcją urządzeń składanych jest tryb książki, w którym urządzenie jest otwarte w połowie, a zawias jest w pozycji pionowej. Tryb książki świetnie sprawdza się podczas czytania e-booków. Dzięki układowi dwustronicowemu na dużym ekranie składanym, który jest otwarty jak oprawiona książka, tryb książki zapewnia wrażenia podobne do czytania prawdziwej książki.

Można go też używać do fotografowania, jeśli chcesz robić zdjęcia bez użycia rąk w innym formacie obrazu.

Tryb książki zaimplementuj za pomocą tych samych technik co tryb stołu. Jedyna różnica polega na tym, że kod powinien sprawdzać, czy orientacja funkcji składania jest pionowa, a nie pozioma:

Kotlin

fun isBookPosture(foldFeature: FoldingFeature?): Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
        foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
}

Java

boolean isBookPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL);
}

Zmiany rozmiaru okna

Obszar wyświetlania aplikacji może się zmienić w wyniku zmiany konfiguracji urządzenia, np. gdy urządzenie jest złożone lub rozłożone, obrócone lub gdy rozmiar okna jest zmieniany w trybie wielu okien.

Klasa WindowMetricsCalculator w Jetpack WindowManager umożliwia pobieranie bieżących i maksymalnych danych okna. Podobnie jak platforma WindowMetrics wprowadzona w poziomie interfejsu API 30, WindowManager WindowMetrics udostępnia granice okna, ale interfejs API jest wstecznie zgodny z poziomem interfejsu API 14.

Zobacz Używanie klas rozmiaru okna.

Dodatkowe materiały

Przykłady

  • Jetpack WindowManager: przykład użycia biblioteki Jetpack WindowManager
  • Jetcaster : implementacja trybu stołu za pomocą Compose

Codelabs