ピクチャー イン ピクチャー(PIP)は特別なタイプのマルチウィンドウ モードであり、主に 動画再生などがあります。ユーザーは、固定された小さなウィンドウで動画を視聴できます。 アプリ間の移動やコンテンツのブラウジング中に アクセスできます。
PIP は、Android 7.0 で使用可能になったマルチウィンドウ API を利用して、固定された動画オーバーレイ ウィンドウを提供します。アプリに PIP を追加するには、 必要に応じてアクティビティを PIP モードに切り替えて アクティビティが PIP モードのときは、動画の再生が継続されます。
このガイドでは、Compose の動画を使用して Compose の PIP をアプリに追加する方法について説明します。 説明します。Socialite アプリでおすすめのアプリをチェック 実践していきます。
PIP 用にアプリを設定する
AndroidManifest.xml
ファイルのアクティビティ タグで、次の操作を行います。
supportsPictureInPicture
を追加してtrue
に設定すると、 アプリの PIP を使用します。configChanges
を追加して以下に設定します。orientation|screenLayout|screenSize|smallestScreenSize
: アクティビティでレイアウト設定の変更を処理します。これにより、 PIP モードの遷移中にレイアウト変更が発生しても、再起動されません。<activity android:name=".SnippetsActivity" android:exported="true" android:supportsPictureInPicture="true" android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize" android:theme="@style/Theme.Snippets">
Compose コードで、次の操作を行います。
- この拡張機能を
Context
に追加します。この広告表示オプションは複数回使用します アクティビティにアクセスするには、internal fun Context.findActivity(): ComponentActivity { var context = this while (context is ContextWrapper) { if (context is ComponentActivity) return context context = context.baseContext } throw IllegalStateException("Picture in picture should be called in the context of an Activity") }
Android 12 より前の休暇アプリに PiP を追加
Android 12 より前の PIP を追加するには、addOnUserLeaveHintProvider
を使用します。フォロー
Android 12 より前の PIP を追加する手順は次のとおりです。
- バージョン ゲートを追加して、このコードにアクセスできるのはバージョン O~R のみにします。
Context
をキーとしてDisposableEffect
を使用します。DisposableEffect
内で、コマンドが呼び出されたときの動作をonUserLeaveHintProvider
はラムダを使用してトリガーされます。ラムダで、findActivity()
でenterPictureInPictureMode()
を指定し、渡すPictureInPictureParams.Builder().build()
。findActivity()
を使用してaddOnUserLeaveHintListener
を追加し、ラムダを渡します。onDispose
で、findActivity()
を使用してremoveOnUserLeaveHintListener
を追加し、ラムダを渡します。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i("PiP info", "API does not support PiP") }
Android 12 以降で休暇時の PIP アプリを追加
Android 12 以降では、PictureInPictureParams.Builder
は
アプリの動画プレーヤーに渡される修飾子です。
modifier
を作成し、それに対してonGloballyPositioned
を呼び出します。レイアウト 座標は後のステップで使用します。PictureInPictureParams.Builder()
の変数を作成します。- SDK が S 以降かどうかを確認する
if
ステートメントを追加します。スワイプ操作で PiP モードに入るようにするには、ビルダーにsetAutoEnterEnabled
を追加してtrue
に設定します。この方法では、1 行 1 行をenterPictureInPictureMode
。 findActivity()
を使用してsetPictureInPictureParams()
を呼び出します。build()
に発信:builder
を作成して渡します。
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(true) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
ボタンで PIP を追加する
ボタンのクリックで PiP モードに入るには、findActivity()
で enterPictureInPictureMode()
を呼び出します。
パラメータは、以前の
PictureInPictureParams.Builder
。新しいパラメータを設定する必要はありません。
指定することもできます。ただし、ボタンのクリック時にパラメータを変更する場合は、ここで設定できます。
val context = LocalContext.current Button(onClick = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.findActivity().enterPictureInPictureMode( PictureInPictureParams.Builder().build() ) } else { Log.i(PIP_TAG, "API does not support PiP") } }) { Text(text = "Enter PiP mode!") }
PIP モードで UI を処理する
PIP モードに入ると、ユーザーが操作しない限り、アプリの UI 全体が PIP ウィンドウに入ります。 PIP モードでの UI の表示方法を指定します。
まず、アプリが PIP モードになっているかどうかを知る必要があります。次を使用:
OnPictureInPictureModeChangedProvider
を使用します。
以下のコードは、アプリが PIP モードであるかどうかを示しています。
@Composable fun rememberIsInPipMode(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val activity = LocalContext.current.findActivity() var pipMode by remember { mutableStateOf(activity.isInPictureInPictureMode) } DisposableEffect(activity) { val observer = Consumer<PictureInPictureModeChangedInfo> { info -> pipMode = info.isInPictureInPictureMode } activity.addOnPictureInPictureModeChangedListener( observer ) onDispose { activity.removeOnPictureInPictureModeChangedListener(observer) } } return pipMode } else { return false } }
rememberIsInPipMode()
を使用して、表示する UI 要素を切り替えることができます。
アプリが PIP モードに入ったとき:
val inPipMode = rememberIsInPipMode() Column(modifier = modifier) { // This text will only show up when the app is not in PiP mode if (!inPipMode) { Text( text = "Picture in Picture", ) } VideoPlayer() }
アプリが適切なタイミングで PIP モードに入るようにしてください
次のような場合は、アプリを PIP モードにしないでください。
- 動画が停止または一時停止された場合。
- 動画プレーヤーとは別のページを表示している場合
アプリが PIP モードになるタイミングを制御するには、状態を追跡する変数を追加します
mutableStateOf
を使って動画プレーヤーのサイズを確認します。
動画が再生中かどうかに応じて状態を切り替えます
動画プレーヤーの再生中かどうかに基づいて状態を切り替えるには、 クリックします。プレーヤーの状態に応じて、状態変数の状態を切り替えます。 再生中かどうか:
player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { shouldEnterPipMode = isPlaying } })
プレーヤーが解放されたかどうかに基づいて状態を切り替える
プレーヤーがリリースされたら、状態変数を false
に設定します。
fun releasePlayer() { shouldEnterPipMode = false }
状態を使用して PIP モードに入るかどうかを定義する(Android 12 より前)
- 12 より前の PIP を追加するには
DisposableEffect
を使用するため、作成する必要があります。newValue
を設定してrememberUpdatedState
で新しい変数を作成します。 使用します。これにより、更新されたバージョンがDisposableEffect
。 OnUserLeaveHintListener
が呼び出されたときの動作を定義するラムダで、 の呼び出しを囲む状態変数を指定したif
ステートメントをenterPictureInPictureMode()
:val currentShouldEnterPipMode by rememberUpdatedState(newValue = shouldEnterPipMode) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { if (currentShouldEnterPipMode) { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i("PiP info", "API does not support PiP") }
状態を使用して PIP モードに入るかどうかを定義する(Android 12 より後)
状態変数を setAutoEnterEnabled
に渡して、アプリが
最適なタイミングで PIP モードを使用する:
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() // Add autoEnterEnabled for versions S and up if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
setSourceRectHint
を使用してスムーズなアニメーションを実装する
setSourceRectHint
API で PIP 開始時のよりスムーズなアニメーションを作成
モードです。Android 12 以降では、PIP モードの終了時のスムーズなアニメーションも作成されます。この API を PIP ビルダーに追加して、アクティビティのエリアを指定します。
PiP への移行後に確認できます
setSourceRectHint()
をbuilder
に追加するのは、その状態が「 アプリが PIP モードになります。これにより、アプリの実行時にsourceRect
が計算されなくなる PIP を入力する必要はありませんsourceRect
値を設定するには、指定されたlayoutCoordinates
を使用します。 修飾子のonGloballyPositioned
関数から取得できます。builder
でsetSourceRectHint()
を呼び出し、sourceRect
変数を渡します。
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
setAspectRatio
を使用して PIP ウィンドウのアスペクト比を設定する
PiP ウィンドウのアスペクト比を設定するには、特定のアスペクト比を選択するか、プレーヤーの動画サイズの幅と高さを使用します。もし
Media3 Player を使用している場合、Player が null でないこと、および Player の
アスペクト比を設定する前の動画サイズが VideoSize.UNKNOWN
と等しくありません
できます。
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
カスタム プレーヤーを使用する場合は、プレーヤーの高さのアスペクト比を設定する 幅と幅を指定することをおすすめしますプレーヤーがゲーム内の 初期化中にサイズ変更される(有効な境界外にある場合) アスペクト比が異なると、アプリがクラッシュします。必要に応じてチェックを追加します アスペクト比を計算できるタイミング(media3 と同様) 表示されます。
リモート アクションを追加する
PIP ウィンドウにコントロール(再生、一時停止など)を追加したい場合は、
追加するコントロールごとに RemoteAction
。
- ブロードキャスト コントロールの定数を追加します。
// Constant for broadcast receiver const val ACTION_BROADCAST_CONTROL = "broadcast_control" // Intent extras for broadcast controls from Picture-in-Picture mode. const val EXTRA_CONTROL_TYPE = "control_type" const val EXTRA_CONTROL_PLAY = 1 const val EXTRA_CONTROL_PAUSE = 2
- PIP ウィンドウのコントロールの
RemoteActions
のリストを作成します。 - 次に、
BroadcastReceiver
を追加し、onReceive()
をオーバーライドして各ボタンのアクションを設定します。DisposableEffect
を使用して、 受信機とリモコンのアクションです。プレーヤーが廃棄されたら、 受信します。@RequiresApi(Build.VERSION_CODES.O) @Composable fun PlayerBroadcastReceiver(player: Player?) { val isInPipMode = rememberIsInPipMode() if (!isInPipMode || player == null) { // Broadcast receiver is only used if app is in PiP mode and player is non null return } val context = LocalContext.current DisposableEffect(player) { val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if ((intent == null) || (intent.action != ACTION_BROADCAST_CONTROL)) { return } when (intent.getIntExtra(EXTRA_CONTROL_TYPE, 0)) { EXTRA_CONTROL_PAUSE -> player.pause() EXTRA_CONTROL_PLAY -> player.play() } } } ContextCompat.registerReceiver( context, broadcastReceiver, IntentFilter(ACTION_BROADCAST_CONTROL), ContextCompat.RECEIVER_NOT_EXPORTED ) onDispose { context.unregisterReceiver(broadcastReceiver) } } }
- リモート アクションのリストを
PictureInPictureParams.Builder
:val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() builder.setActions( listOfRemoteActions() ) if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(modifier = pipModifier)
次のステップ
このガイドでは、Android 12 以前と Android 12 以降の両方で Compose に PiP を追加する際のベスト プラクティスについて学習しました。
- Socialite アプリで、 Compose PIP の動作
- 詳しくは、PIP 設計ガイダンスをご覧ください。