Duży rozłożony wyświetlacz i unikalny tryb złożenia zapewniają nowe wrażenia użytkownikom składanych urządzeń. Aby o tym poinformować, użyj biblioteki Jetpack WindowManager, która udostępnia interfejs API dla funkcji składanych okien, takich jak składanie i zawiasy. Jeśli aplikacja jest złożona, może dostosować swój układ, aby uniknąć umieszczania ważnych treści w obszarze zagięcia lub zawiasów, oraz stosować zagięcia i zawiasy jako naturalne separatory.
Informacje o oknie
Interfejs WindowInfoTracker
w Jetpack WindowManager udostępnia informacje o układzie okien. Metoda windowLayoutInfo()
interfejsu zwraca strumień danych WindowLayoutInfo
, które informują aplikację o stanie złożenia urządzenia. Metoda WindowInfoTracker
getOrCreate()
tworzy wystąpienie elementu WindowInfoTracker
.
WindowManager obsługuje zbieranie danych WindowLayoutInfo
za pomocą Kotlin Flows i wywołań zwrotnych Java.
Kotlin Flows
Aby rozpocząć i zatrzymać zbieranie danych w usłudze WindowLayoutInfo
, możesz użyć korektywy rejestrującej cykl życia z możliwością ponownego uruchomienia, w której blok kodu repeatOnLifecycle
jest wykonywany, gdy cykl życia ma co najmniej STARTED
, a jest zatrzymany, gdy cykl życia to STOPPED
. Wykonanie bloku kodu jest automatycznie wznawiane, gdy cykl życia wynosi 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 Java
Warstwa zgodności wywołania zwrotnego zawartej w zależności androidx.window:window-java
umożliwia zbieranie aktualizacji WindowLayoutInfo
bez użycia przepływu Kotlin. Artefakt zawiera klasę WindowInfoTrackerCallbackAdapter
, która dostosowuje WindowInfoTracker
do obsługi rejestrowania (i wyrejestrowania) 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
(wersja 2
lub 3
), możesz skorzystać z artefaktów umożliwiających korzystanie z artefaktów Observable
lub Flowable
do zbierania aktualizacji WindowLayoutInfo
bez użycia usługi Kotlin Flow.
Warstwa zgodności zapewniana 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 aplikacji WindowLayoutInfo
, 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 = 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 the WindowLayoutInfo observable
disposable?.dispose()
}
}
Funkcje składanych ekranów
Klasa WindowLayoutInfo
Jetpack WindowManager udostępnia funkcje okna wyświetlania w postaci listy elementów DisplayFeature
.
FoldingFeature
to rodzaj tagu DisplayFeature
, który zawiera informacje o składanych ekranach, m.in.:
state
: stan złożenia urządzenia (FLAT
lubHALF_OPENED
).orientation
: orientacja złożenia lub zawiasu:HORIZONTAL
lubVERTICAL
.occlusionType
: określa, czy składanie lub zawias zakrywa część wyświetlacza,NONE
czyFULL
isSeparating
: określa, czy zawijanie 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. Dodatkowo na urządzeniach z 2 ekranami zasada isSeparating
ma zawsze wartość prawda, gdy aplikacja obejmuje oba ekrany.
Właściwość FoldingFeature
bounds
(odziedziczona z DisplayFeature
) reprezentuje prostokąt blokujący elementy składane, takie jak składanie lub zawias. Granice mogą służyć do określania pozycji elementów na ekranie względem obiektu.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { // Safely collects from windowInfoRepo 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() .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 na stole
Dzięki informacjom zawartym w obiekcie FoldingFeature
aplikacja może obsługiwać różne pozycje, takie jak tryb stołu, zawias telefonu umieszczony na powierzchni, zawias w pozycji poziomej, a składany ekran jest w połowie otwarty.
Tryb Na stole zapewnia użytkownikom wygodę obsługi telefonu bez konieczności trzymania go w dłoniach. Tryb Na stole doskonale sprawdza się do oglądania multimediów, robienia zdjęć i rozmów wideo.
Za pomocą funkcji FoldingFeature.State
i FoldingFeature.Orientation
określ, czy urządzenie jest w trybie Na stole:
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 działa w trybie Na stole, zaktualizuj odpowiednio układ aplikacji. W przypadku aplikacji multimedialnych oznacza to zazwyczaj umieszczenie odtwarzania w części strony widocznej na ekranie oraz elementów sterujących pozycjonowaniem i dodatkowymi treściami tuż poniżej, aby umożliwić oglądanie i słuchanie bez użycia rąk.
Przykłady
Aplikacja
MediaPlayerActivity
: zobacz, jak za pomocą Media3 Exoplayer i WindowManagera utworzyć składany odtwarzacz wideo.Ćwiczenie z programowania dotyczące rozwijania funkcji aparatu: dowiedz się, jak wdrożyć tryb stołu w aplikacjach fotograficznych. Pokaż wizjer w górnej części ekranu, w części strony widocznej na ekranie, a elementy sterujące na dole, w części strony widocznej po przewinięciu.
Tryb książki
Innym przykładem urządzenia, który można złożyć, jest tryb książki, w którym urządzenie jest do połowy otwarte, a zawias – pionowy. Tryb książki doskonale nadaje się do czytania e-booków. Dwustronicowy układ na dużym, składanym ekranie, tak jak książka na oprawie, umożliwia czytanie prawdziwej książki.
Można go też używać do fotografowania, jeśli chcesz uchwycić inny format obrazu podczas robienia zdjęć bez użycia rąk.
Zaimplementuj tryb książki za pomocą tych samych technik, które są stosowane w trybie 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); }
Zmiana rozmiaru okna
Wraz ze zmianą konfiguracji urządzenia, np. po złożeniu lub rozłożeniu, obróceniu lub zmianie rozmiaru okna w trybie wielu okien, może również zmieniać się obszar wyświetlania aplikacji.
Klasa Jetpack WindowManager WindowMetricsCalculator
umożliwia pobieranie bieżących i maksymalnych wskaźników okna. Podobnie jak platforma WindowMetrics
wprowadzona w interfejsie API poziomu 30, WindowManager WindowMetrics
wyznacza granice okien, ale interfejs API jest zgodny wstecznie do poziomu API 14.
Informacje o obsłudze różnych rozmiarów okien znajdziesz w artykule Obsługa różnych rozmiarów ekranu.
Dodatkowe materiały
Próbki
- Jetpack WindowManager: przykład korzystania z biblioteki Jetpack WindowManager
- Jetcaster: implementacja stanu stołu za pomocą funkcji Compose