Складные устройства предлагают уникальные впечатления от просмотра. Режим заднего дисплея и режим двойного экрана позволяют создавать специальные функции дисплея для складных устройств, такие как предварительный просмотр селфи на задней камере и одновременное, но разное отображение на внутреннем и внешнем экранах.
Режим заднего дисплея
Обычно, когда складное устройство развернуто, активен только внутренний экран. Режим заднего дисплея позволяет переместить действие на внешний экран складного устройства, который обычно обращен от пользователя, когда устройство развернуто. Внутренний дисплей автоматически выключается.
Новое приложение предназначено для отображения предварительного просмотра камеры на внешнем экране, чтобы пользователи могли делать селфи с помощью задней камеры, которая обычно обеспечивает гораздо лучшую производительность съемки, чем передняя камера.
Чтобы активировать режим заднего дисплея, пользователи отвечают на диалоговое окно, позволяющее приложению переключать экраны, например:
Диалог создает система, поэтому никаких доработок с вашей стороны не требуется. В зависимости от состояния устройства появляются разные диалоговые окна; например, система предлагает пользователям развернуть устройство, если оно закрыто. Вы не можете настроить диалоговое окно, и оно может различаться на устройствах разных OEM-производителей.
Вы можете опробовать режим заднего дисплея с помощью приложения камеры Pixel Fold. См. пример реализации в лаборатории кода. Оптимизируйте приложение камеры на складных устройствах с помощью Jetpack WindowManager .
Двухэкранный режим
Режим двух экранов позволяет отображать контент на обоих дисплеях складного устройства одновременно. Режим двух экранов доступен на устройствах Pixel Fold под управлением Android 14 (уровень API 34) или более поздней версии.
Примером использования является переводчик с двумя экранами.
Включить режимы программно
Доступ к режиму заднего дисплея и режиму двойного экрана можно получить через API-интерфейсы Jetpack WindowManager , начиная с версии библиотеки 1.2.0-beta03.
Добавьте зависимость WindowManager в файл build.gradle
модуля вашего приложения:
классный
dependencies { implementation "androidx.window:window:1.2.0-beta03" }
Котлин
dependencies { implementation("androidx.window:window:1.2.0-beta03") }
Точкой входа является WindowAreaController
, который предоставляет информацию и поведение, связанные с перемещением окон между дисплеями или между областями отображения на устройстве. WindowAreaController
позволяет запрашивать список доступных объектов WindowAreaInfo
.
Используйте WindowAreaInfo
для доступа к WindowAreaSession
— интерфейсу, который представляет функцию активной области окна. Используйте WindowAreaSession
, чтобы определить доступность определенного WindowAreaCapability
.
Каждая возможность связана с определенной WindowAreaCapability.Operation
. В версии 1.2.0-beta03 Jetpack WindowManager поддерживает два типа операций:
-
WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA
, который используется для запуска режима двух экранов. -
WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
, который используется для запуска режима заднего дисплея.
Ниже приведен пример объявления переменных для режима заднего дисплея и режима двойного экрана в основной активности вашего приложения:
Котлин
private lateinit var windowAreaController: WindowAreaController private lateinit var displayExecutor: Executor private var windowAreaSession: WindowAreaSession? = null private var windowAreaInfo: WindowAreaInfo? = null private var capabilityStatus: WindowAreaCapability.Status = WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED private val dualScreenOperation = WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA private val rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
Ява
private WindowAreaControllerCallbackAdapter windowAreaController = null; private Executor displayExecutor = null; private WindowAreaSessionPresenter windowAreaSession = null; private WindowAreaInfo windowAreaInfo = null; private WindowAreaCapability.Status capabilityStatus = WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED; private WindowAreaCapability.Operation dualScreenOperation = WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA; private WindowAreaCapability.Operation rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA;
Вот как инициализировать переменные в методе onCreate()
вашей активности:
Котлин
displayExecutor = ContextCompat.getMainExecutor(this) windowAreaController = WindowAreaController.getOrCreate() lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { windowAreaController.windowAreaInfos .map { info -> info.firstOrNull { it.type == WindowAreaInfo.Type.TYPE_REAR_FACING } } .onEach { info -> windowAreaInfo = info } .map { it?.getCapability(operation)?.status ?: WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED } .distinctUntilChanged() .collect { capabilityStatus = it } } }
Ява
displayExecutor = ContextCompat.getMainExecutor(this); windowAreaController = new WindowAreaControllerCallbackAdapter(WindowAreaController.getOrCreate()); windowAreaController.addWindowAreaInfoListListener(displayExecutor, this); windowAreaController.addWindowAreaInfoListListener(displayExecutor, windowAreaInfos -> { for(WindowAreaInfo newInfo : windowAreaInfos){ if(newInfo.getType().equals(WindowAreaInfo.Type.TYPE_REAR_FACING)){ windowAreaInfo = newInfo; capabilityStatus = newInfo.getCapability(presentOperation).getStatus(); break; } } });
Перед началом операции проверьте наличие конкретной возможности:
Котлин
when (capabilityStatus) { WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> { // The selected display mode is not supported on this device. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE -> { // The selected display mode is not available. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> { // The selected display mode is available and can be enabled. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> { // The selected display mode is already active. } else -> { // The selected display mode status is unknown. } }
Ява
if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED)) { // The selected display mode is not supported on this device. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE)) { // The selected display mode is not available. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE)) { // The selected display mode is available and can be enabled. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE)) { // The selected display mode is already active. } else { // The selected display mode status is unknown. }
Двухэкранный режим
В следующем примере сеанс закрывается, если возможность уже активна, или иным образом вызывает функцию presentContentOnWindowArea()
:
Котлин
fun toggleDualScreenMode() { if (windowAreaSession != null) { windowAreaSession?.close() } else { windowAreaInfo?.token?.let { token -> windowAreaController.presentContentOnWindowArea( token = token, activity = this, executor = displayExecutor, windowAreaPresentationSessionCallback = this ) } } }
Ява
private void toggleDualScreenMode() { if(windowAreaSession != null) { windowAreaSession.close(); } else { Binder token = windowAreaInfo.getToken(); windowAreaController.presentContentOnWindowArea( token, this, displayExecutor, this); } }
Обратите внимание на использование основного действия приложения в качестве аргумента WindowAreaPresentationSessionCallback
.
API использует подход прослушивателя: когда вы делаете запрос на представление содержимого на другом дисплее складного объекта, вы инициируете сеанс, который возвращается через метод onSessionStarted()
прослушивателя. Когда вы закрываете сеанс, вы получаете подтверждение в методе onSessionEnded()
.
Чтобы создать прослушиватель, реализуйте интерфейс WindowAreaPresentationSessionCallback
:
Котлин
class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback
Ява
public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback
Слушателю необходимо реализовать методы onSessionStarted()
, onSessionEnded(),
и onContainerVisibilityChanged()
. Методы обратного вызова уведомляют вас о состоянии сеанса и позволяют соответствующим образом обновить приложение.
Обратный вызов onSessionStarted()
получает WindowAreaSessionPresenter
в качестве аргумента. Аргументом является контейнер, который позволяет вам получить доступ к области окна и отобразить содержимое. Презентация может быть автоматически закрыта системой, когда пользователь покидает основное окно приложения, или презентацию можно закрыть, вызвав WindowAreaSessionPresenter#close()
.
Что касается других обратных вызовов, для простоты просто проверьте тело функции на наличие ошибок и запишите состояние:
Котлин
override fun onSessionStarted(session: WindowAreaSessionPresenter) { windowAreaSession = session val view = TextView(session.context) view.text = "Hello world!" session.setContentView(view) } override fun onSessionEnded(t: Throwable?) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}") } } override fun onContainerVisibilityChanged(isVisible: Boolean) { Log.d(logTag, "onContainerVisibilityChanged. isVisible = $isVisible") }
Ява
@Override public void onSessionStarted(@NonNull WindowAreaSessionPresenter session) { windowAreaSession = session; TextView view = new TextView(session.getContext()); view.setText("Hello world, from the other screen!"); session.setContentView(view); } @Override public void onSessionEnded(@Nullable Throwable t) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}"); } } @Override public void onContainerVisibilityChanged(boolean isVisible) { Log.d(logTag, "onContainerVisibilityChanged. isVisible = " + isVisible); }
Чтобы обеспечить согласованность всей экосистемы, используйте официальный значок двойного экрана , чтобы указать пользователям, как включить или отключить режим двойного экрана.
Рабочий пример см. в DualScreenActivity.kt .
Режим заднего дисплея
Подобно примеру с режимом двойного экрана, следующий пример функции toggleRearDisplayMode()
закрывает сеанс, если эта возможность уже активна, или иным образом вызывает функцию transferActivityToWindowArea()
:
Котлин
fun toggleRearDisplayMode() { if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) { if(windowAreaSession == null) { windowAreaSession = windowAreaInfo?.getActiveSession( operation ) } windowAreaSession?.close() } else { windowAreaInfo?.token?.let { token -> windowAreaController.transferActivityToWindowArea( token = token, activity = this, executor = displayExecutor, windowAreaSessionCallback = this ) } } }
Ява
void toggleDualScreenMode() { if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) { if(windowAreaSession == null) { windowAreaSession = windowAreaInfo.getActiveSession( operation ) } windowAreaSession.close() } else { Binder token = windowAreaInfo.getToken(); windowAreaController.transferActivityToWindowArea(token, this, displayExecutor, this); } }
В этом случае отображаемая активность используется как WindowAreaSessionCallback
, что проще реализовать, поскольку обратный вызов не получает презентера, который позволяет отображать контент в области окна, а вместо этого переносит всю активность в другую область:
Котлин
override fun onSessionStarted() { Log.d(logTag, "onSessionStarted") } override fun onSessionEnded(t: Throwable?) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}") } }
Ява
@Override public void onSessionStarted(){ Log.d(logTag, "onSessionStarted"); } @Override public void onSessionEnded(@Nullable Throwable t) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}"); } }
Чтобы обеспечить согласованность всей экосистемы, используйте официальный значок задней камеры, чтобы указать пользователям, как включить или отключить режим заднего дисплея.
Дополнительные ресурсы
- Оптимизируйте приложение камеры на складных устройствах с помощью лаборатории кода Jetpack WindowManager.
- Сводка пакета
androidx.window.area
- Пример кода Jetpack WindowManager: