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 Jetpack WindowManager udostępnia informacje o układzie okna. Metoda windowLayoutInfo() interfejsu 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 umożliwia zbieranie danych WindowLayoutInfo za pomocą przepływów Kotlin i wywołań zwrotnych Java.
Przepływy w Kotlinie
Aby rozpocząć i zatrzymać zbieranie danych WindowLayoutInfo, możesz użyć współprogramu z możliwością ponownego uruchomienia, który 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 skorzystać 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-rxjava2 i androidx.window:window-rxjava3 zawiera metody WindowInfoTracker#windowLayoutInfoFlowable() i 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 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,FLATlubHALF_OPENEDorientation: orientacja zagięcia lub zawiasu,HORIZONTALlubVERTICALocclusionType: czy zagięcie lub zawias zasłania część wyświetlacza (NONElubFULL).isSeparating: Czy zagięcie lub zawias tworzy 2 obszary wyświetlania, prawda lub fałsz
Urządzenie składane, które HALF_OPENED zawsze zgłasza isSeparating jako wartość prawdziwą, 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ć pozycje takie 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 konieczności trzymania go w dłoni. Pozycja na stole świetnie się sprawdza podczas oglądania multimediów, robienia zdjęć i prowadzenia rozmów wideo.
Użyj FoldingFeature.State i FoldingFeature.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 w części strony widocznej na ekranie 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
MediaPlayerActivity– aplikacja: dowiedz się, jak używać Media3 Exoplayer i WindowManager do tworzenia odtwarzacza wideo z uwzględnieniem zgięcia.Optymalizacja aplikacji aparatu na urządzeniach składanych za pomocą biblioteki Jetpack WindowManager Codelab: dowiedz się, jak wdrożyć tryb stołowy w aplikacjach do fotografowania. Wyświetl wizjer w górnej połowie ekranu (część widoczna na ekranie), a elementy sterujące w dolnej połowie (część widoczna po przewinięciu).
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. W przypadku dwustronicowego układu na dużym ekranie składanego urządzenia otwartego jak książka w oprawie tryb 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, WindowManagerWindowMetrics udostępnia granice okna, ale interfejs API 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 na stole za pomocą Compose
Codelabs
- Obsługa urządzeń składanych i z 2 ekranami za pomocą Jetpack WindowManager
- Optymalizowanie aplikacji Aparat na urządzeniach składanych za pomocą Jetpack WindowManager