Informuj o zwijaniu aplikacji

Duże rozłożone wyświetlacze i unikalny stan złożenia zwiększają wygodę korzystania z urządzeń składanych. Aby aplikacja zawierała informacje o składaniu aplikacji, użyj biblioteki Jetpack WindowManager, która udostępnia interfejs API do obsługi funkcji w oknie urządzenia składanego, takich jak złożenie czy zawias. Gdy aplikacja jest dobrze widoczna na ekranie, może dostosować swój układ tak, aby uniknąć umieszczania ważnych treści w częściach fałdowych lub zawiasach, i używać zawiasów jako naturalnych separatorów.

Wiedza o tym, czy urządzenie obsługuje konfiguracje takie jak ułożenie na stole czy w księgu, może pomóc w podejmowaniu decyzji o obsługiwaniu 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() interfejsu zwraca strumień danych WindowLayoutInfo, który informuje aplikację o stanie składania składanego urządzenia. Metoda WindowInfoTracker#getOrCreate() tworzy instancję klasy WindowInfoTracker.

OknoWindowManager obsługuje zbieranie danych WindowLayoutInfo za pomocą przepływów Kotlin i wywołań zwrotnych w Javie.

Przepływy w Kotlinie

Aby rozpocząć i zatrzymać zbieranie danych WindowLayoutInfo, możesz użyć przerywanej coroutine z uwzględnieniem cyklu życia, w której blok kodu repeatOnLifecycle jest wykonywany, gdy cykl życia jest co najmniej STARTED, i zatrzymywany, gdy cykl życia jest STOPPED. Wykonywanie bloku kodu jest automatycznie wznawiane, gdy cykl życia jest ponownie w stanie STARTED. W tym przykładzie blok kodu zbiera i używa danych WindowLayoutInfo:

class DisplayFeaturesActivity : AppCompatActivity() {

    private lateinit var binding: ActivityDisplayFeaturesBinding

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

        binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
        setContentView(binding.root)

        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 w Javie

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

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 korzystasz już z RxJava (wersja 2 lub 3), możesz skorzystać z artefaktów, które umożliwiają gromadzenie aktualizacji WindowLayoutInfo bez użycia procesu Kotlin za pomocą modelu Observable lub Flowable.

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

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 = ActivitySplitLayoutBinding.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 składanych wyświetlaczy

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

FoldingFeature to typ DisplayFeature, który zawiera informacje o ekranach składanych, w tym:

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

  • orientation: orientacja zagięcia lub zawiasów, HORIZONTAL lub VERTICAL

  • occlusionType: czy zawias lub zawias ukrywa część wyświetlacza: NONE lub FULL

  • isSeparating: czy złożenie lub zawias tworzy 2 logiczne obszary wyświetlacza (prawda lub fałsz).

Urządzenie składane w trybie HALF_OPENED zawsze zwraca wartość isSeparating jako prawda, ponieważ ekran jest podzielony na 2 obszary wyświetlacza. Ponadto na urządzeniu z dwoma ekranami isSeparating jest zawsze prawdą, gdy aplikacja obejmuje oba ekrany.

Właściwość FoldingFeature bounds (odziedziczona z DisplayFeature) reprezentuje prostokąt ograniczający element składany, np. zawias. Za pomocą tych granic możesz umieszczać elementy 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.
            }
        }
    }
}

Postawa przy stole

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

Konstrukcja stołu sprawia, że użytkownicy mogą wygodnie obsługiwać telefon bez trzymania go w rękach. Pozycja na stole jest idealna do oglądania multimediów, robienia zdjęć i prowadzenia rozmów wideo.

Rysunek 1. Aplikacja odtwarzacza wideo w stanie stołu.

Za pomocą FoldingFeature.State i FoldingFeature.Orientation możesz określić, czy urządzenie znajduje się w stanie stołu:

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 urządzenie jest w pozycji poziomej, zaktualizuj układ aplikacji. W przypadku aplikacji multimedialnych oznacza to zwykle umieszczenie odtwarzania nad resztą interfejsu oraz elementów sterujących i treści dodatkowych poniżej, aby umożliwić oglądanie lub słuchanie bez użycia rąk.

W Androidzie 15 (poziom interfejsu API 35) i nowszych możesz wywołać interfejs API synchronicznego, aby wykryć, czy urządzenie obsługuje pozycję na stole niezależnie od jego bieżącego stanu.

Interfejs API zawiera listę stanów obsługiwanych przez urządzenie. Jeśli lista zawiera pozycję stołu, możesz podzielić układ aplikacji, aby uwzględnić tę pozycję, a następnie przeprowadzić testy A/B interfejsu aplikacji w układzie na stół i w układzie pełnoekranowym.

Kotlin

if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) {
    val postures = WindowInfoTracker.getOrCreate(context).supportedPostures
    if (postures.contains(TABLE_TOP)) {
        // 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

Stan książki

Kolejną unikalną funkcją składanego urządzenia jest tryb książki, w którym urządzenie jest częściowo otwarte, a zawias jest pionowy. Pozycja książkowa jest świetna do czytania e-booków. Dzięki dwustronicowemu układowi na dużym, składanym ekranie (tak jak w powiązanych książkach) postawa książki uchwyca emocje z prawdziwej książki.

Możesz też używać go do robienia zdjęć, jeśli chcesz robić zdjęcia w innym formacie, nie używając rąk.

Stosuj tę samą technikę, co w przypadku pozycji na stole. Jedyną różnicą jest to, że kod powinien sprawdzać, czy funkcja zwijania ma orientację pionową, a nie poziomą.

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 będzie złożone lub rozłożone, obrócone albo gdy zmienisz rozmiar okna w trybie wielu okien.

Klasa Jetpack WindowManager WindowMetricsCalculator umożliwia pobieranie bieżących i maksymalnych danych o oknie. Podobnie jak platforma WindowMetrics wprowadzona na poziomie 30 interfejsu API, WindowManagerWindowMetrics określa granice okna, ale interfejs API jest zgodny wstecz do poziomu 14.

Zobacz Korzystanie z klas rozmiarów okien.

Dodatkowe materiały

Próbki

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

Ćwiczenia z programowania