Obsługa urządzeń składanych na 3 części i składanych w orientacji poziomej

Składany telefon w orientacji poziomej w pozycji zamkniętej i całkowicie otwartej obok składanego telefonu w pozycji zamkniętej i całkowicie otwartej.

Tworząc aplikacje na urządzenia składane, programiści często napotykają wyjątkowe trudności, zwłaszcza w przypadku urządzeń takich jak Samsung Trifold czy oryginalny Pixel Fold, które otwierają się w orientacji poziomej (rotation_0 = landscape). Błędy popełniane przez programistów:

  • błędne założenia dotyczące orientacji urządzenia,
  • pominięte przypadki użycia,
  • niepowodzenie w ponownym obliczeniu lub zapisaniu w pamięci podręcznej wartości po zmianach konfiguracji.

Problemy związane z konkretnymi urządzeniami:

  • Niezgodność naturalnej orientacji urządzenia między ekranem zewnętrznym a wewnętrznym (założenia oparte na rotation_0 = portrait), co powoduje awarie aplikacji podczas składania i rozkładania urządzenia.
  • Różne gęstości ekranu i nieprawidłowa obsługa zmian konfiguracji density.
  • Problemy z podglądem z aparatu spowodowane zależnością czujnika aparatu od naturalnej orientacji.

Aby zapewnić wysoką jakość obsługi na urządzeniach składanych, skup się na tych najważniejszych obszarach:

  • Określ orientację aplikacji na podstawie rzeczywistego obszaru ekranu, który zajmuje aplikacja, a nie fizycznej orientacji urządzenia.
  • Aktualizuj podglądy z aparatu, aby prawidłowo zarządzać orientacją urządzenia i formatami obrazu, unikać podglądów wyświetlanych bokiem oraz zapobiegać rozciąganiu lub przycinaniu obrazów.
  • Zapewnij ciągłość działania aplikacji podczas składania lub rozkładania urządzenia, zachowując stan za pomocą ViewModel lub podobnych metod albo ręcznie obsługując zmiany gęstości i orientacji ekranu, co pozwala uniknąć ponownego uruchomienia aplikacji lub utraty stanu.
  • W przypadku aplikacji korzystających z czujników ruchu dostosuj układ współrzędnych, aby był zgodny z bieżącą orientacją ekranu, i unikaj założeń opartych na rotation_0 = portrait, co gwarantuje precyzyjne interakcje użytkownika.

Tworzenie aplikacji adaptacyjnych

Jeśli Twoja aplikacja jest już adaptacyjna i spełnia wymagania poziomu zoptymalizowanego (poziom 2) określone we wskazówkach dotyczących jakości aplikacji adaptacyjnych, powinna dobrze działać na urządzeniach składanych. W przeciwnym razie, zanim sprawdzisz szczegóły dotyczące urządzeń składanych z 3 ekranami i urządzeń składanych w orientacji poziomej, zapoznaj się z tymi podstawowymi koncepcjami tworzenia aplikacji adaptacyjnych na Androida.

Układy adaptacyjne

Interfejs użytkownika musi obsługiwać nie tylko różne rozmiary ekranu, ale też zmiany formatu obrazu w czasie rzeczywistym, np. podczas rozkładania urządzenia i przechodzenia do trybu wielu okien lub trybu okien na pulpicie. Więcej informacji o tym, jak:

  • projektować i wdrażać układy adaptacyjne,
  • dostosowywać główną nawigację aplikacji do rozmiaru okna,
  • używać klas rozmiaru okna do dostosowywania interfejsu aplikacji,
  • upraszczać wdrażanie układów kanonicznych, takich jak lista-szczegóły, za pomocą interfejsów Jetpack API.
Aplikacja w formacie letterbox na otwartym urządzeniu składanym i ta sama aplikacja w trybie pełnoekranowym z układem adaptacyjnym na innym otwartym urządzeniu składanym.
Rysunek 1. Różnica między układami nieadaptacyjnymi (z czarnymi pasami) a adaptacyjnymi.

Klasy rozmiaru okna

Urządzenia składane, w tym urządzenia składane w orientacji poziomej i urządzenia z 3 ekranami, mogą natychmiast przełączać się między klasami rozmiaru okna: kompaktową, średnią i rozszerzoną. Zrozumienie i wdrożenie tych klas gwarantuje, że aplikacja będzie wyświetlać prawidłowe komponenty nawigacyjne i gęstość treści w zależności od bieżącego stanu urządzenia.

Przedstawienie aplikacji na urządzeniach o rozmiarach należących do klas kompaktowej, średniej i rozszerzonej.
Rysunek 2. Klasy rozmiaru okna.

W tym przykładzie używamy biblioteki adaptacyjnej Material 3, aby określić, ile miejsca ma aplikacja. Najpierw wywołujemy funkcję currentWindowAdaptiveInfo(), a potem używamy odpowiednich układów dla 3 klas rozmiaru okna:

val adaptiveInfo = currentWindowAdaptiveInfo()
val windowSizeClass = adaptiveInfo.windowSizeClass

when {
  windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_EXPANDED_LOWER_BOUND) -> // Expanded
  windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND) -> // Medium
  else -> // Compact
}

Więcej informacji znajdziesz w artykule Używanie klas rozmiaru okna.

Jakość aplikacji adaptacyjnych

Przestrzeganie poziomu 2 (aplikacja adaptacyjna zoptymalizowana) lub poziomu 1 (aplikacja adaptacyjna zróżnicowana) we wskazówkach dotyczących jakości aplikacji adaptacyjnych gwarantuje, że aplikacja będzie zapewniać atrakcyjne wrażenia użytkownikom urządzeń z 3 ekranami, urządzeń składanych w orientacji poziomej i innych urządzeń z dużym ekranem. Wskazówki obejmują najważniejsze kontrole na różnych poziomach, które pozwalają przejść od aplikacji adaptacyjnej do aplikacji zróżnicowanej.

Android 16 i nowsze wersje

W przypadku aplikacji kierowanych na Androida 16 (interfejs API na poziomie 36) i nowsze wersje system ignoruje ograniczenia dotyczące orientacji, możliwości zmiany rozmiaru i formatu obrazu na wyświetlaczach o najmniejszej szerokości >= 600 dp. Aplikacje wypełniają całe okno wyświetlacza niezależnie od formatu obrazu i preferowanej orientacji użytkownika, a tryb zgodności z czarnymi pasami nie jest już używany.

Specjalne uwagi

Urządzenia z 3 ekranami i urządzenia składane w orientacji poziomej mają unikalne zachowania sprzętowe, które wymagają specjalnej obsługi, zwłaszcza w przypadku czujników, podglądu z aparatu i ciągłości konfiguracji (zachowywania stanu podczas składania, rozkładania lub zmiany rozmiaru).

Podgląd z aparatu

Częstym problemem na urządzeniach składanych w orientacji poziomej lub podczas obliczania formatu obrazu (w scenariuszach takich jak tryb wielu okien, tryb okien na pulpicie lub podłączone wyświetlacze) jest rozciągnięty, obrócony, przycięty lub wyświetlany bokiem podgląd z aparatu.

Niezgodne założenia

Ten problem często występuje na urządzeniach z dużym ekranem i urządzeniach składanych, ponieważ aplikacje mogą zakładać stałe relacje między funkcjami aparatu (takimi jak format obrazu i orientacja czujnika) a funkcjami urządzenia (takimi jak orientacja urządzenia i naturalna orientacja).

Nowe formaty podważają to założenie. Urządzenie składane może zmieniać rozmiar wyświetlacza i format obrazu bez zmiany orientacji urządzenia. Na przykład rozłożenie urządzenia zmienia format obrazu, ale jeśli użytkownik nie obróci urządzenia, jego orientacja pozostanie taka sama. Jeśli aplikacja zakłada, że format obrazu jest powiązany z orientacją urządzenia, może nieprawidłowo obracać lub skalować podgląd z aparatu. To samo może się zdarzyć, jeśli aplikacja zakłada, że orientacja czujnika aparatu jest zgodna z orientacją urządzenia w trybie pionowym, co nie zawsze jest prawdą w przypadku urządzeń składanych w orientacji poziomej.

Rozwiązanie 1. Jetpack CameraX (najlepsze)

Najprostszym i najbardziej niezawodnym rozwiązaniem jest użycie biblioteki Jetpack CameraX. Element interfejsu PreviewView został zaprojektowany tak, aby automatycznie obsługiwać wszystkie złożone aspekty podglądu:

  • PreviewView prawidłowo dostosowuje się do orientacji czujnika, orientacji urządzenia i skalowania.
  • Zachowuje format obrazu z aparatu, zwykle przez wyśrodkowanie i przycięcie (FILL_CENTER).
  • W razie potrzeby możesz ustawić typ skalowania na FIT_CENTER, aby dodać czarne pasy do podglądu.

Więcej informacji znajdziesz w dokumentacji CameraX w artykule Implementowanie podglądu.

Rozwiązanie 2. CameraViewfinder

Jeśli używasz istniejącej bazy kodu Camera2, biblioteka CameraViewfinder (zgodna wstecznie z interfejsem API na poziomie 21) jest kolejnym nowoczesnym rozwiązaniem. Upraszcza wyświetlanie przekazu z kamery za pomocą TextureView lub SurfaceView i stosuje wszystkie niezbędne przekształcenia (format obrazu, skalowanie i obrót).

Więcej informacji znajdziesz w poście na blogu Introducing Camera Viewfinder i w przewodniku dla programistów dotyczącym podglądu z aparatu.

Rozwiązanie 3. Ręczna implementacja Camera2

Jeśli nie możesz używać CameraX ani CameraViewfinder, musisz ręcznie obliczyć orientację i format obrazu oraz zadbać o to, aby obliczenia były aktualizowane przy każdej zmianie konfiguracji:

  • Pobierz orientację czujnika aparatu (np. 0, 90, 180, 270 stopni) z CameraCharacteristics.
  • Pobierz bieżącą orientację wyświetlacza urządzenia (np. 0, 90, 180, 270 stopni).
  • Użyj tych 2 wartości, aby określić niezbędne przekształcenia dla SurfaceView lub TextureView.
  • Aby uniknąć zniekształceń, upewnij się, że format obrazu wyjściowego Surface jest zgodny z formatem obrazu podglądu z aparatu.
  • Aplikacja aparatu może działać w części ekranu, w trybie wielu okien, w trybie okien na pulpicie lub na podłączonym wyświetlaczu. Z tego powodu do określania wymiarów wizjera aparatu nie należy używać rozmiaru ekranu, ale danych okna.

Więcej informacji znajdziesz w przewodniku dla programistów dotyczącym podglądu z aparatu i w filmie Your Camera app on different form factors.

Rozwiązanie 4. Wykonywanie podstawowych działań aparatu za pomocą intencji

Jeśli nie potrzebujesz wielu funkcji aparatu, prostym rozwiązaniem jest wykonywanie podstawowych działań aparatu, takich jak robienie zdjęć lub nagrywanie filmów, za pomocą domyślnej aplikacji aparatu na urządzeniu. Nie musisz integrować się z biblioteką aparatu. Zamiast tego użyj intencji.

Więcej informacji znajdziesz w artykule Intencje aparatu.

Konfiguracja i ciągłość

Urządzenia składane zwiększają wszechstronność interfejsu, ale mogą inicjować więcej zmian konfiguracji niż urządzenia nieskładane. Aplikacja musi zarządzać tymi zmianami konfiguracji i ich kombinacjami, takimi jak orientacja urządzenia, składanie/rozkładanie i zmiana rozmiaru okna w trybie wielu okien lub trybie okien na pulpicie, przy jednoczesnym zachowaniu lub przywracaniu stanu aplikacji. Na przykład aplikacje muszą zachowywać ciągłość w tych obszarach:

  • Stan aplikacji bez awarii lub powodowania zakłócających zmian dla użytkowników (np. podczas przełączania ekranów lub wysyłania aplikacji w tle).
  • Pozycja przewijania pól z możliwością przewijania.
  • Tekst wpisany w polach tekstowych i stan klawiatury.
  • Pozycja odtwarzania multimediów, aby odtwarzanie było wznawiane w miejscu, w którym zostało przerwane po zainicjowaniu zmiany konfiguracji.

Często wywoływane zmiany konfiguracji to screenSize, smallestScreenSize, screenLayout, orientation, density, fontScale, touchscreen i keyboard.

Zobacz android:configChanges i Obsługa zmian konfiguracji. Dodatkowe informacje o zarządzaniu stanem aplikacji znajdziesz w artykule Zapisywanie stanów interfejsu.

Zmiany konfiguracji gęstości

Zewnętrzny i wewnętrzny ekran urządzeń z 3 ekranami i urządzeń składanych w orientacji poziomej mogą mieć różne gęstości pikseli. Dlatego zarządzanie zmianą konfiguracji density wymaga dodatkowej uwagi. Android zwykle ponownie uruchamia aktywność, gdy zmienia się gęstość wyświetlacza, co może spowodować utratę danych. Aby zapobiec ponownemu uruchamianiu Activity przez system, zadeklaruj obsługę gęstości w pliku manifestu i zarządzaj zmianą konfiguracji programowo w aplikacji.

Konfiguracja AndroidManifest.xml

  • density: deklaruje, że aplikacja będzie obsługiwać zmianę gęstości ekranu.
  • Inne zmiany konfiguracji: warto też zadeklarować inne często występujące zmiany konfiguracji, np. screenSize, orientation, keyboardHidden, fontScale itp.

Deklarowanie gęstości (i innych zmian konfiguracji) zapobiega ponownemu uruchamianiu aktywności przez system i zamiast tego wywołuje onConfigurationChanged().

Implementacja onConfigurationChanged()

Gdy nastąpi zmiana gęstości, musisz zaktualizować zasoby (np. ponownie wczytać mapy bitowe lub ponownie obliczyć rozmiary układu) w wywołaniu zwrotnym:

  • Sprawdź, czy DPI zmieniło się na newConfig.densityDpi.
  • Zresetuj widoki niestandardowe, niestandardowe elementy rysowalne itp. do nowej gęstości.

Elementy zasobów do przetworzenia

  • Zasób obrazu: zastąp mapy bitowe i elementy rysowalne zasobami o określonej gęstości lub bezpośrednio dostosuj skalę.
  • Jednostka układu (konwersja dp na px): ponownie oblicz rozmiar widoku, margines, dopełnienie.
  • Rozmiar czcionki i tekstu: ponownie zastosuj rozmiar tekstu w jednostkach sp.
  • Rysowanie niestandardowego View/Canvas: zaktualizuj wartości oparte na pikselach używane do rysowania Canvas

Określanie orientacji aplikacji

Podczas tworzenia aplikacji adaptacyjnych nigdy nie polegaj na orientacji urządzenia fizycznego, ponieważ będzie ona ignorowana na urządzeniach z dużym ekranem a aplikacja w trybie wielu okien może mieć inną orientację niż urządzenie. Zamiast tego użyj Configuration.orientation lub WindowMetrics, aby określić, czy aplikacja jest obecnie w orientacji poziomej czy pionowej na podstawie rozmiaru okna.

Rozwiązanie 1. Użyj Configuration.orientation

Ta właściwość określa orientację, w której jest obecnie wyświetlana aplikacja.

Rozwiązanie 2. Użyj WindowMetrics#getBounds()

Możesz pobrać bieżące granice wyświetlacza aplikacji i sprawdzić jej szerokość oraz wysokość, aby określić orientację.

Jeśli chcesz ograniczyć orientację aplikacji na telefonach (lub zewnętrznych ekranach urządzeń składanych), ale nie na urządzeniach z dużym ekranem, zobacz Ograniczanie orientacji aplikacji na telefonach.

Pozycje i tryby wyświetlania

Pozycje i stany urządzeń składanych, takie jak tryb stołu i HALF_OPENED, są obsługiwane zarówno przez urządzenia składane w orientacji pionowej, jak i poziomej. Urządzenia z 3 ekranami nie obsługują jednak trybu stołu i nie można ich używać w stanie HALF_OPENED. Zamiast tego urządzenia z 3 ekranami oferują większy ekran, który zapewnia wyjątkowe wrażenia użytkownikom po pełnym rozłożeniu.

Aby wyróżnić aplikację na urządzeniach składanych, które obsługują HALF_OPENED, użyj interfejsów Jetpack WindowManager API, takich jak FoldingFeature.

Więcej informacji o pozycjach i stanach urządzeń składanych oraz o obsłudze podglądu z aparatu znajdziesz w tych przewodnikach dla programistów:

Urządzenia składane oferują wyjątkowe wrażenia podczas oglądania. Tryb wyświetlania na tylnym ekranie i tryb podwójnego ekranu umożliwiają tworzenie specjalnych funkcji wyświetlania na urządzeniach składanych, takich jak podgląd selfie z tylnego aparatu i jednoczesne, ale różne wyświetlanie na ekranach wewnętrznym i zewnętrznym. Więcej informacji znajdziesz w tych artykułach:

Blokowanie orientacji na naturalną orientację czujnika

W bardzo konkretnych przypadkach użycia – zwłaszcza w przypadku aplikacji, które muszą zajmować cały ekran niezależnie od stanu złożenia urządzenia – flaga nosensor umożliwia zablokowanie aplikacji w naturalnej orientacji urządzenia. Na przykład w przypadku Pixela Fold naturalna orientacja urządzenia po złożeniu to orientacja pionowa, a po rozłożeniu – pozioma. Dodanie flagi nosensor powoduje, że aplikacja jest zablokowana w orientacji pionowej, gdy działa na ekranie zewnętrznym, oraz w orientacji poziomej, gdy działa na ekranie wewnętrznym.

<activity
  android:name=".MainActivity"
  android:screenOrientation="nosensor">

Gry i ponowne mapowanie czujników XR

W przypadku gier i aplikacji XR surowe dane z czujników (np. z żyroskopu lub akcelerometru) są udostępniane w układzie współrzędnych stałym dla urządzenia. Jeśli użytkownik obróci urządzenie, aby grać w grę w orientacji poziomej, osie czujnika nie będą się obracać wraz z ekranem, co spowoduje nieprawidłowe sterowanie w grze.

Aby rozwiązać ten problem, sprawdź bieżącą wartość Display.getRotation() i odpowiednio zmapuj osie:

  • Obrót 0: x=x, y=y
  • Obrót 90: x=-y, y=x
  • Obrót 180: x=-x, y=-y
  • Obrót 270: x=y, y=-x

W przypadku wektorów obrotu (używanych w kompasie lub aplikacjach XR) użyj SensorManager.remapCoordinateSystem(), aby zmapować kierunek obiektywu aparatu lub górną część ekranu na nowe osie na podstawie bieżącego obrotu.

Zgodność aplikacji

Aby zapewnić zgodność ze wszystkimi formatami i podłączonymi wyświetlaczami, aplikacje muszą być zgodne ze wskazówkami dotyczącymi jakości aplikacji. Jeśli aplikacja nie spełnia tych wskazówek, producenci urządzeń mogą wdrożyć rozwiązania zapewniające zgodność, ale może to pogorszyć wrażenia użytkownika.

Dodatkowe informacje znajdziesz w obszernej liście obejść problemów ze zgodnością dostępnych na platformie, w szczególności tych związanych z podglądem z aparatu , zastąpieniami i zmianami w interfejsie Android 16 API, które mogą zmienić zachowanie aplikacji.

Więcej informacji o tworzeniu aplikacji adaptacyjnych znajdziesz we wskazówkach dotyczących jakości aplikacji adaptacyjnych.