Informuj o zwijaniu aplikacji

Duże, rozłożone wyświetlacze i wyjątkowe stany złożenia umożliwiają nowe sposoby korzystania z urządzeń składanych. Aby dostosować aplikację do urządzeń składanych, użyj biblioteki Jetpack WindowManager, która udostępnia interfejs API do obsługi funkcji okna urządzenia składanego, takich jak zgięcia i zawiasy. Gdy aplikacja jest dostosowana do urządzeń składanych, może dostosowywać układ, aby uniknąć umieszczania ważnych treści w obszarze zagięć lub zawiasów, a także wykorzystywać zagięcia i zawiasy jako naturalne separatory.

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

Informacje o oknie

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

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

Przepływy Kotlin

Aby rozpocząć i zatrzymać zbieranie danych WindowLayoutInfo, możesz użyć koryginy z możliwością ponownego uruchomienia, która uwzględnia cykl życia. Blok kodu repeatOnLifecycle jest w niej wykonywany, gdy cykl życia osiągnie stan co najmniej STARTED, a zatrzymywany, gdy cykl życia osiągnie stan 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 : 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 przepływu Kotlin. Artefakt zawiera klasę WindowInfoTrackerCallbackAdapter, która dostosowuje WindowInfoTracker, aby obsługiwać rejestrowanie (i wyrejestrowywanie) 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 używasz już RxJava (wersja 2 lub 3), możesz korzystać z artefaktów, które umożliwiają używanie Observable lub Flowable do zbierania aktualizacji WindowLayoutInfo bez używania przepływu Kotlin.

Warstwa zgodności udostępniana przez zależności androidx.window:window-rxjava2androidx.window:window-rxjava3 zawiera metody WindowInfoTracker#windowLayoutInfoFlowable()WindowInfoTracker#windowLayoutInfoObservable(), które umożliwiają aplikacji 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 wyświetlaczy składanych

Klasa WindowLayoutInfo biblioteki Jetpack WindowManager udostępnia funkcje okna wyświetlania w postaci listy 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 obszary wyświetlania, prawda lub fałsz

Urządzenie składane HALF_OPENED zawsze zgłasza wartość isSeparating jako „true”, ponieważ ekran jest podzielony na 2 obszary wyświetlania. W przypadku urządzenia z dwoma ekranami wartość isSeparating jest zawsze prawdziwa, gdy aplikacja zajmuje oba ekrany.

Właściwość FoldingFeature bounds (dziedziczona z DisplayFeature) reprezentuje prostokąt ograniczający element składany, taki 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.
            }
        }
    }
}

Pozycja na stole

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

W pozycji stołowej użytkownicy mogą wygodnie obsługiwać telefon bez trzymania go w rękach. Pozycja na stole świetnie się sprawdza podczas oglądania multimediów, robienia zdjęć i prowadzenia rozmów wideo.

Rysunek 1. Aplikacja odtwarzacza wideo w pozycji stołowej – wideo na pionowej części ekranu, elementy sterujące odtwarzaniem na części poziomej.

Użyj FoldingFeature.StateFoldingFeature.Orientation, aby określić, czy urządzenie jest w pozycji stołowej:

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 będzie w pozycji stołowej, odpowiednio zaktualizuj układ aplikacji. W przypadku aplikacji multimedialnych oznacza to zwykle umieszczenie odtwarzania powyżej linii podziału i umieszczenie elementów sterujących oraz treści dodatkowych tuż za nią, aby zapewnić możliwość oglądania lub słuchania bez użycia rąk.

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

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

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 rezerwacji

Kolejną unikalną funkcją urządzenia składanego jest tryb książki, w którym urządzenie jest otwarte w połowie, a zawias jest ustawiony pionowo. Tryb książki świetnie sprawdza się podczas czytania e-booków. Układ dwustronicowy na dużym ekranie składanego urządzenia otwartego jak książka w oprawie w trybie książki odzwierciedla wrażenia podczas czytania prawdziwej książki.

Możesz go też używać do robienia zdjęć, jeśli chcesz uchwycić inny format obrazu, robiąc zdjęcia bez użycia rąk.

Wprowadź pozycję książki, stosując te same techniki co w przypadku pozycji na stole. 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ę zmieniać w wyniku zmiany konfiguracji urządzenia, np. gdy urządzenie jest składane lub rozkładane, obracane albo gdy rozmiar okna jest zmieniany w trybie wielu okien.

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

Zobacz Korzystanie z klas rozmiarów okien.

Dodatkowe materiały

Próbki

  • Jetpack WindowManager: przykład użycia biblioteki Jetpack WindowManager
  • Jetcaster : implementacja postawy przy stole za pomocą Compose

Codelabs