折りたたみ式デバイスでは、折りたたみ式ならではの視聴エクスペリエンスを実現できます。背面ディスプレイ モードと デュアル スクリーン モードでは、折りたたみ式デバイス向けの特別なディスプレイ機能を構築可能 背面カメラ セルフィー プレビューや、同時ではあるものの異なる画面 内外のディスプレイに表示されます。
背面ディスプレイ モード
通常、折りたたみ式デバイスを開くと、内側の画面のみがアクティブになります。 背面ディスプレイ モードでは、アクティビティを折りたたみ式の外側の画面に移動できます (通常は、デバイスを開いた状態ではユーザーの反対側を向く)「 インナー ディスプレイが自動的にオフになります。
新しいアプリは、外側の画面にカメラのプレビューを表示するというものです。 背面カメラで自撮り写真を撮影できるため、通常は背面カメラで 前面カメラより高精細な写真を撮影できます。
背面ディスプレイ モードを有効にするには、ユーザーがダイアログに応答して、アプリに以下の操作を許可します。 画面を切り替えます。たとえば、
ダイアログはシステムによって作成されるため、デベロッパー側での開発は必要ありません。 デバイスの状態に応じて異なるダイアログが表示されます。たとえば、デバイスが閉じている場合は、デバイスを開くようシステムがユーザーに指示します。カスタマイズはできません デバイスに応じて OEM ごとに異なる場合があります。
Google Pixel Fold のカメラアプリで背面ディスプレイ モードを試すことができます。Codelab のJetpack WindowManager を使用して折りたたみ式デバイスのカメラアプリを最適化するの実装例をご覧ください。
デュアル スクリーン モード
デュアル スクリーン モードでは、折りたたみ式デバイスの両方のディスプレイに同時にコンテンツを表示できます。デュアル スクリーン モードは Android 14 を搭載した Google Pixel Fold で利用できます (API レベル 34)以降が必要です。
ユースケースの一例として、デュアル スクリーンの通訳があります。
モードをプログラマティックに有効にする
ライブラリ バージョン 1.2.0-beta03 以降では、Jetpack WindowManager API を使用して、背面ディスプレイ モードとデュアル スクリーン モードにアクセスできます。
アプリのモジュール build.gradle
ファイルに WindowManager の依存関係を追加します。
Groovy
dependencies { implementation "androidx.window:window:1.2.0-beta03" }
Kotlin
dependencies { implementation("androidx.window:window:1.2.0-beta03") }
エントリ ポイントは WindowAreaController
です。これにより、
ディスプレイ間やディスプレイ間のウィンドウの移動に関する情報や動作
デバイスの表示領域ですWindowAreaController
を使用すると、使用可能な WindowAreaInfo
オブジェクトのリストを確認できます。
WindowAreaInfo
を使用して、WindowAreaSession
というインターフェースにアクセスします。
アクティブなウィンドウ領域機能を表します。WindowAreaSession
を使用して決定する
特定の WindowAreaCapability
の可用性。
各 capability は特定の WindowAreaCapability.Operation
に関連付けられています。バージョン 1.2.0-beta03 では、Jetpack WindowManager は次の 2 種類のオペレーションをサポートしています。
WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA
: デュアル スクリーン モードを開始します。WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
, 背面ディスプレイ モードを開始します。
背面ディスプレイ モードの変数を宣言する方法の例と、 デュアル スクリーン モードをアプリのメイン アクティビティで使用する必要があります。
Kotlin
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
Java
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()
メソッドで変数を初期化する方法は次のとおりです。
アクティビティ:
Kotlin
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 } } }
Java
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; } } });
オペレーションを開始する前に、特定の capability について使用可否を確認します。
Kotlin
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. } }
Java
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()
関数を呼び出します。
Kotlin
fun toggleDualScreenMode() { if (windowAreaSession != null) { windowAreaSession?.close() } else { windowAreaInfo?.token?.let { token -> windowAreaController.presentContentOnWindowArea( token = token, activity = this, executor = displayExecutor, windowAreaPresentationSessionCallback = this ) } } }
Java
private void toggleDualScreenMode() { if(windowAreaSession != null) { windowAreaSession.close(); } else { Binder token = windowAreaInfo.getToken(); windowAreaController.presentContentOnWindowArea( token, this, displayExecutor, this); } }
ここでは、アプリのメイン アクティビティを
WindowAreaPresentationSessionCallback
引数。
この API はリスナー アプローチを使用します。折りたたみ式デバイスの反対側のディスプレイにコンテンツを表示するリクエストを行うと、リスナーの onSessionStarted()
メソッドを通じて返されるセッションが開始されます。セッションを閉じると、onSessionEnded()
メソッドに確認メッセージが表示されます。
リスナーを作成するには、WindowAreaPresentationSessionCallback
インターフェースを実装します。
Kotlin
class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback
Java
public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback
リスナーは onSessionStarted()
、onSessionEnded(),
を実装する必要があります。
および onContainerVisibilityChanged()
メソッドを使用します。このコールバック メソッドが
通知が表示され、それに応じてアプリをアップデートできます。
onSessionStarted()
コールバックは、WindowAreaSessionPresenter
を
渡します。この引数は、ウィンドウ領域にアクセスできるようにするコンテナです。
表示されます。プレゼンテーションは、ユーザーがメインのアプリケーション ウィンドウを閉じたときに、システムによって自動的に閉じることができます。または、WindowAreaSessionPresenter#close()
を呼び出してプレゼンテーションを閉じることもできます。
他のコールバックでは、わかりやすくするために、関数本体でエラーを確認し、状態をログに記録します。
Kotlin
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") }
Java
@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()
関数の例では、capability がすでにアクティブな場合はセッションを閉じます。そうでない場合は transferActivityToWindowArea()
関数を呼び出します。
Kotlin
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 ) } } }
Java
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
として使用されます。
コールバックはプレゼンターを受け取る必要がないため、実装が簡単です。
ウィンドウ領域にコンテンツを表示できますが、
移動することもできます。
Kotlin
override fun onSessionStarted() { Log.d(logTag, "onSessionStarted") } override fun onSessionEnded(t: Throwable?) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}") } }
Java
@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}"); } }
エコシステム全体で一貫性を保つため、Rear Camera(背面カメラ)公式の アイコンで、背面ディスプレイ モードを有効または無効にする方法をユーザーに示します。
参考情報
- Jetpack WindowManager を使用して折りたたみ式デバイスのカメラアプリを最適化する Codelab
androidx.window.area
パッケージの概要- Jetpack WindowManager サンプルコード: