Большие развернутые дисплеи и уникальные состояния в сложенном состоянии открывают новые возможности для пользователей на складных устройствах. Чтобы обеспечить поддержку складывания вашего приложения, используйте библиотеку Jetpack WindowManager , которая предоставляет поверхность API для таких функций складных окон устройств, как складки и петли. Когда ваше приложение поддерживает складку, оно может адаптировать свой макет, чтобы избежать размещения важного контента в области сгибов или петель и использовать складки и петли в качестве естественных разделителей.
Информация об окне
Интерфейс WindowInfoTracker
в Jetpack WindowManager предоставляет информацию о макете окна. Метод windowLayoutInfo()
интерфейса возвращает поток данных WindowLayoutInfo
, который информирует ваше приложение о состоянии складывания складного устройства. Метод WindowInfoTracker
getOrCreate()
создает экземпляр WindowInfoTracker
.
WindowManager обеспечивает поддержку сбора данных WindowLayoutInfo
с помощью Kotlin Flows и обратных вызовов Java.
Котлин потоки
Чтобы запустить и остановить сбор данных WindowLayoutInfo
, вы можете использовать перезапускаемую сопрограмму с поддержкой жизненного цикла, в которой блок кода repeatOnLifecycle
выполняется, когда жизненный цикл по крайней мере STARTED
, и останавливается, когда жизненный цикл STOPPED
. Выполнение блока кода автоматически перезапускается, когда жизненный цикл снова STARTED
. В следующем примере блок кода собирает и использует данные 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.
}
}
}
}
}
Обратные вызовы Java
Уровень совместимости обратного вызова, включенный в зависимость androidx.window:window-java
позволяет собирать обновления WindowLayoutInfo
без использования Kotlin Flow. Артефакт включает в себя класс WindowInfoTrackerCallbackAdapter
, который адаптирует WindowInfoTracker
для поддержки регистрации (и отмены регистрации) обратных вызовов для получения обновлений WindowLayoutInfo
, например:
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.
});
}
}
}
Поддержка RxJava
Если вы уже используете RxJava
(версия 2
или 3
), вы можете воспользоваться артефактами, которые позволяют использовать Observable
или Flowable
для сбора обновлений WindowLayoutInfo
без использования Kotlin Flow.
Уровень совместимости, предоставляемый зависимостями androidx.window:window-rxjava2
и androidx.window:window-rxjava3
включает методы WindowInfoTracker#windowLayoutInfoFlowable()
и WindowInfoTracker#windowLayoutInfoObservable()
, которые позволяют вашему приложению получать обновления WindowLayoutInfo
, например:
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()
}
}
Особенности складных дисплеев
Класс WindowLayoutInfo
Jetpack WindowManager делает функции окна отображения доступными в виде списка элементов DisplayFeature
.
FoldingFeature
— это тип DisplayFeature
, который предоставляет информацию о складных дисплеях, включая следующее:
-
state
: сложенное состояние устройства,FLAT
илиHALF_OPENED
-
orientation
: ориентация сгиба или петли:HORIZONTAL
илиVERTICAL
-
occlusionType
: скрывает ли складка или шарнир часть дисплея,NONE
илиFULL
-
isSeparating
: создает ли сгиб или шарнир две логические области отображения: true или false.
Складное устройство с параметром HALF_OPENED
всегда сообщает isSeparating
как true, поскольку экран разделен на две области отображения. Кроме того, isSeparating
всегда имеет значение true на устройстве с двумя экранами, когда приложение охватывает оба экрана.
Свойство bounds
FoldingFeature
(наследованное от DisplayFeature
) представляет ограничивающий прямоугольник элемента сгиба, такого как сгиб или шарнир. Границы можно использовать для позиционирования элементов на экране относительно объекта.
Котлин
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 } } } }
Ява
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 } } } }
Настольный режим
Используя информацию, содержащуюся в объекте FoldingFeature
, ваше приложение может поддерживать такие положения, как настольный режим, когда телефон находится на поверхности, шарнир находится в горизонтальном положении, а складной экран наполовину открыт.
Настольный режим предлагает пользователям удобство управления телефоном, не держа его в руках. Настольный режим отлично подходит для просмотра мультимедиа, фотосъемки и видеозвонков.
Используйте FoldingFeature.State
и FoldingFeature.Orientation
, чтобы определить, находится ли устройство в настольном режиме:
Котлин
fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL }
Ява
boolean isTableTopPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL); }
Как только вы узнаете, что устройство находится в настольном режиме, соответствующим образом обновите макет приложения. Для мультимедийных приложений это обычно означает размещение воспроизведения над сгибом и размещение элементов управления и дополнительного контента чуть ниже для просмотра или прослушивания без помощи рук.
Примеры
Приложение
MediaPlayerActivity
: узнайте, как использовать Media3 Exoplayer и WindowManager для создания складного видеопроигрывателя.Лаборатория кода «Раскройте возможности камеры» : узнайте, как реализовать настольный режим для приложений для фотографий. Покажите видоискатель в верхней половине экрана над сгибом, а элементы управления — в нижней половине под сгибом.
Режим книги
Еще одно уникальное положение складывания — книжный режим, в котором устройство наполовину открыто, а шарнир расположен вертикально. Режим книги отлично подходит для чтения электронных книг. Благодаря двухстраничному макету на большом экране, который можно сложить, как переплетенную книгу, режим книги передает ощущение чтения настоящей книги.
Его также можно использовать для фотографии, если вы хотите запечатлеть другое соотношение сторон при съемке без помощи рук.
Реализуйте книжный режим, используя те же методы, что и для настольного режима. Единственное отличие состоит в том, что код должен проверять, что ориентация элемента сгиба вертикальная, а не горизонтальная:
Котлин
fun isBookPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.VERTICAL }
Ява
boolean isBookPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL); }
Изменение размера окна
Область отображения приложения может измениться в результате изменения конфигурации устройства — например, когда устройство складывается или разворачивается, поворачивается или изменяется размер окна в многооконном режиме.
Класс Jetpack WindowManager WindowMetricsCalculator
позволяет получать текущие и максимальные метрики окна. Как и платформа WindowMetrics
представленная на уровне API 30, WindowManager WindowMetrics
обеспечивает границы окна, но API обратно совместим вплоть до уровня API 14.
См. Классы размеров окон .
Дополнительные ресурсы
Образцы
- Jetpack WindowManager : пример использования библиотеки Jetpack WindowManager.
- Jetcaster : реализация положения стола с помощью Compose
Кодлабы
- Поддержка складных устройств и устройств с двумя экранами с помощью Jetpack WindowManager.
- Раскройте свои впечатления от камеры
Рекомендуется для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Поддержка складных устройств и устройств с двумя экранами с помощью Jetpack WindowManager.
- Оптимизируйте приложение камеры на складных устройствах с помощью Jetpack WindowManager.
- Режим совместимости устройств