Android 8.0(API レベル 26)以降では、アクティビティをピクチャー イン ピクチャー(PIP)モードで起動できます。PiP は、動画再生、ビデオ通話、ナビゲーションに使用される特殊なマルチウィンドウ モードです。この機能を使用すると、ユーザーはメイン画面でアプリを切り替えたりコンテンツをブラウジングしたりしながら、既存のアクティビティ ウィンドウを画面の隅に固定できます。
PIP は、Android 7.0 で使用可能になったマルチウィンドウ API を利用して、固定された動画オーバーレイ ウィンドウを提供します。アプリで PIP を使用するには、PIP をサポートするアクティビティを登録し、必要に応じてアクティビティを PIP モードに切り替える必要があります。そして、アクティビティが PIP モードのときには、UI 要素を非表示にして動画の再生を継続するようにします。
PiP ウィンドウは、画面の最上位レイヤの、システムによって選択された隅に表示されます。
PiP は、Android 14(API レベル 34)以降を搭載した対応 Android TV OS デバイスでもサポートされています。多くの類似点がありますが、TV で PiP を使用する場合は、追加の考慮事項があります。
ユーザーが PIP ウィンドウを操作する方法
ユーザーは、PIP ウィンドウを別の位置にドラッグできます。Android 12 以降では、次のことも行えます。
PIP ウィンドウをシングルタップすると、全画面表示への切り替え、閉じるボタン、設定ボタン、アプリが提供するカスタム アクション(再生コントロールなど)が表示されます。
ウィンドウをダブルタップすると、現在の PiP サイズと最大または最小の PiP サイズが切り替わります。たとえば、最大化されたウィンドウをダブルタップすると最小化され、その逆も同様です。
ウィンドウを左端または右端にドラッグして、ウィンドウを隠します。ウィンドウのスタッシュを解除するには、スタッシュされたウィンドウの表示部分をタップするか、ドラッグします。
ピンチ操作で PIP ウィンドウのサイズを変更できます。
現在のアクティビティが PIP モードに入るタイミングは、アプリで制御します。次に例を示します。
ユーザーがホームボタンをタップするか、または画面を下から上にスワイプしてホーム画面に移動したら、アクティビティを PIP モードに切り替えることができます(たとえば、Google マップは、ユーザーが別のアクティビティを同時に実行している間、この方法でルートを表示し続けます)。
ユーザーが動画から戻って他のコンテンツを閲覧するときに、アプリで動画を PIP モードに移行できます。
ユーザーが見ている動画コンテンツがエピソードの終盤に差し掛かったら、動画を PIP モードに切り替えます。メイン画面には、シリーズの次のエピソードに関する宣伝情報やあらすじを表示します。
ユーザーが動画を見ながら他のコンテンツをキューに追加できるようにします。メイン画面にコンテンツ選択アクティビティが表示されている間も、動画は PiP モードで再生され続けます。
PIP のサポートを宣言する
デフォルトでは、システムはアプリの PiP を自動的にサポートしません。アプリで PiP をサポートする場合は、android:supportsPictureInPicture を true に設定して、マニフェストに動画アクティビティを登録します。また、アクティビティがレイアウト構成の変更を処理するように指定して、ピクチャー イン ピクチャー モードの移行中にレイアウトの変更が発生したときにアクティビティが再起動しないようにします。
<activity android:name="VideoActivity"
android:supportsPictureInPicture="true"
android:configChanges=
"screenSize|smallestScreenSize|screenLayout|orientation"
...
Jetpack で PiP を実装する
Jetpack ピクチャー イン ピクチャー ライブラリを使用して、ピクチャー イン ピクチャー エクスペリエンスを実装します。統合が効率化され、アプリ内での一般的な問題が軽減されます。使用例については、プラットフォーム サンプルアプリをご覧ください。ただし、プラットフォーム API を使用して PiP を実装する場合は、次のドキュメントを参照してください。
アクティビティを PiP に切り替える
Android 12 以降では、setAutoEnterEnabled フラグを true に設定することで、アクティビティをピクチャー イン ピクチャー モードに切り替えることができます。この設定を使用すると、onUserLeaveHint で enterPictureInPictureMode() を明示的に呼び出すことなく、必要に応じてアクティビティが自動的に PiP モードに切り替わります。これにより、移行がよりスムーズになるというメリットもあります。詳細については、ジェスチャー ナビゲーションから PiP モードへの移行をスムーズにするをご覧ください。
Android 11 以前をターゲットとする場合、アクティビティは enterPictureInPictureMode() を呼び出して PiP モードに切り替える必要があります。たとえば、次のコードは、ユーザーがアプリの UI で専用のボタンをクリックしたときに、アクティビティを PIP モードに切り替えます。
override fun onActionClicked(action: Action) {
if (action.id.toInt() == R.id.lb_control_picture_in_picture) {
activity?.enterPictureInPictureMode()
return
}
}
アクティビティをバックグラウンドに移行するのではなく、ピクチャー イン ピクチャー モードに切り替えるロジックを含めることもできます。たとえば、Google マップは、ユーザーがホームボタンまたは最近ボタンを押して別のアプリに移動すると、PIP モードに切り替わります。このケースをキャッチするには、onUserLeaveHint() をオーバーライドします。
override fun onUserLeaveHint() {
if (iWantToBeInPipModeNow()) {
enterPictureInPictureMode()
}
}
推奨: 洗練された PiP 遷移エクスペリエンスをユーザーに提供する
Android 12 では、全画面表示と PIP ウィンドウ間のアニメーション遷移に大幅な外観の改善が加えられました。該当する変更をすべて実装することを強くおすすめします。実装すると、これらの変更は、それ以上の作業を必要とせずに、折りたたみ式デバイスやタブレットなどの大画面に自動的にスケーリングされます。
アプリに該当するアップデートが含まれていない場合でも、PiP の切り替えは機能しますが、アニメーションの洗練度は低くなります。たとえば、全画面表示からピクチャー イン ピクチャー モードに移行すると、移行中にピクチャー イン ピクチャー ウィンドウが消え、移行が完了すると再び表示されることがあります。
これらの変更には、次のものが含まれます。
- ジェスチャー ナビゲーションからピクチャー イン ピクチャー モードへの移行をスムーズにする
- ピクチャー イン ピクチャー モードの開始と終了に適切な
sourceRectHintを設定する - 動画コンテンツ以外のコンテンツでシームレスなサイズ変更を無効にする
洗練された遷移エクスペリエンスを実現するためのリファレンスとして、Android Kotlin PictureInPicture サンプルをご覧ください。
ジェスチャー ナビゲーションでの PIP モードへの遷移をよりスムーズにする
Android 12 以降では、setAutoEnterEnabled フラグにより、ジェスチャー ナビゲーションを使用して PIP モードで動画コンテンツに移行する際のアニメーションが大幅にスムーズになります。たとえば、全画面表示から上にスワイプしてホームに移動する場合などです。
この変更を行う手順は次のとおりです。
setAutoEnterEnabledを使用してPictureInPictureParams.Builderを作成します。
setPictureInPictureParams(PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
.setSourceRectHint(sourceRectHint)
.setAutoEnterEnabled(true)
.build())
Note: When `setAutoEnterEnabled` is enabled, you don't need to explicitly
call [`enterPictureInPictureMode`][5] in [`onUserLeaveHint`][6].
最新の
PictureInPictureParamsを使って、早い段階でsetPictureInPictureParamsを呼び出します。アプリはonUserLeaveHintコールバックを待機しません(Android 11 では待機していました)。たとえば、最初に行う再生と、アスペクト比が変更された後に行う再生で、
setPictureInPictureParamsを呼び出すことができます。setAutoEnterEnabled(false)を呼び出します。ただし、必要な場合に限ります。たとえば、現在の再生が一時停止状態の場合、PiP に入ることは望ましくありません。
ピクチャー イン ピクチャー モードの開始と終了に適切な sourceRectHint を設定する
Android 8.0 で PiP が導入されて以降、setSourceRectHint は、ピクチャー イン ピクチャーへの移行後に表示されるアクティビティの領域(動画プレーヤーの動画ビューの境界など)を示していました。
Android 12 では、システムは sourceRectHint を使用して、PIP モードの開始時と終了時の両方で、よりスムーズなアニメーションを実装します。
ピクチャー イン ピクチャー モードの開始と終了を適切に設定するには、sourceRectHint を次のように設定します。
sourceRectHintとして適切な境界を使用してPictureInPictureParamsを作成します。また、動画プレーヤーにレイアウト変更リスナーをアタッチすることをおすすめします。
val mOnLayoutChangeListener =
OnLayoutChangeListener { v: View?, left: Int,
top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop:
Int, oldRight: Int, oldBottom: Int ->
val sourceRectHint = Rect()
mYourVideoView.getGlobalVisibleRect(sourceRectHint)
val builder = PictureInPictureParams.Builder()
.setSourceRectHint(sourceRectHint)
setPictureInPictureParams(builder.build())
}
mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
- 必要に応じて、システムが終了遷移を開始する前に
sourceRectHintを更新します。システムがピクチャー イン ピクチャー モードを終了しようとすると、アクティビティのビュー階層が、その宛先の構成(全画面表示など)にレイアウトされます。アプリは、ルートビューまたはターゲット ビュー(動画プレーヤー ビューなど)にレイアウト変更リスナーをアタッチして、イベントを検出し、アニメーションが開始される前にsourceRectHintを更新できます。
// Listener is called immediately after the user exits PiP but before animating.
playerView.addOnLayoutChangeListener { _, left, top, right, bottom,
oldLeft, oldTop, oldRight, oldBottom ->
if (left != oldLeft
|| right != oldRight
|| top != oldTop
|| bottom != oldBottom) {
// The playerView's bounds changed, update the source hint rect to
// reflect its new bounds.
val sourceRectHint = Rect()
playerView.getGlobalVisibleRect(sourceRectHint)
setPictureInPictureParams(
PictureInPictureParams.Builder()
.setSourceRectHint(sourceRectHint)
.build()
)
}
}
動画コンテンツ以外のコンテンツのシームレスなサイズ変更を無効にする
Android 12 では setSeamlessResizeEnabled フラグが追加され、PiP ウィンドウで動画以外のコンテンツのサイズを変更する際に、よりスムーズなクロスフェード アニメーションが提供されます。以前は、PiP ウィンドウで動画以外のコンテンツのサイズを変更すると、不快な視覚的アーティファクトが発生することがありました。
動画コンテンツのシームレスなサイズ変更を有効にするには:
setPictureInPictureParams(PictureInPictureParams.Builder()
.setSeamlessResizeEnabled(true)
.build())
PIP 中の UI を処理する
アクティビティがピクチャー イン ピクチャー(PIP)モードを開始または終了すると、システムは Activity.onPictureInPictureModeChanged() または Fragment.onPictureInPictureModeChanged() を呼び出します。
Android 15 では、PIP モードへの移行をさらにスムーズにする変更が導入されています。これは、メイン UI の上に UI 要素がオーバーレイされているアプリが PiP に移行する場合に便利です。
デベロッパーは、onPictureInPictureModeChanged() コールバックを使用して、オーバーレイされた UI 要素の表示を切り替えるロジックを定義します。このコールバックは、PiP の開始または終了のアニメーションが完了したときにトリガーされます。Android 15 以降、PictureInPictureUiState クラスに新しい状態が追加されました。
この新しい UI 状態により、Android 15 をターゲットとするアプリは、PiP アニメーションが開始されるとすぐに isTransitioningToPip() で Activity#onPictureInPictureUiStateChanged() コールバックが呼び出されることを確認します。アプリが PiP モードのときには、関連性のない UI 要素が多数あります。たとえば、おすすめ、次の動画、評価、タイトルなどの情報を含むビューやレイアウトです。アプリが PiP モードに移行したら、onPictureInPictureUiStateChanged() コールバックを使用してこれらの UI 要素を非表示にします。アプリが PIP ウィンドウから全画面モードに移行するときは、次の例に示すように、onPictureInPictureModeChanged() コールバックを使用してこれらの要素を再表示します。
override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
if (pipState.isTransitioningToPip()) {
// Hide UI elements.
}
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (!isInPictureInPictureMode) {
// Unhide UI elements.
}
}
(PiP ウィンドウの)無関係な UI 要素の表示をすばやく切り替えることで、PiP 開始アニメーションがスムーズになり、ちらつきがなくなります。
これらのコールバックをオーバーライドして、アクティビティの UI 要素を再描画します。PiP モードでは、アクティビティが小さなウィンドウに表示されます。アプリがピクチャー イン ピクチャー モードの場合、ユーザーはアプリの UI 要素を操作できません。また、小さな UI 要素の詳細は見えにくい場合があります。動画再生アクティビティでは、UI の数を最小限にすることがユーザー エクスペリエンスの向上につながります。
アプリで PiP のカスタム アクションを提供する必要がある場合は、このページのコントロールを追加するをご覧ください。アクティビティがピクチャー イン ピクチャーに切り替わる前に他の UI 要素を削除し、アクティビティが全画面表示に戻ったときにそれらの要素を復元します。
コントロールの追加
PIP ウィンドウでは、ユーザーが(モバイル デバイスでウィンドウをタップするか、テレビリモコンでメニューを選択することにより)ウィンドウのメニューを開いたときに、コントロールを表示できます。
アプリにアクティブなメディア セッションがある場合は、再生、一時停止、次へ、前へのコントロールが表示されます。
PIP モードに切り替える前に PictureInPictureParams.Builder.setActions() で PictureInPictureParams をビルドして、カスタム アクションを明示的に指定することもできます。PIP モードに切り替えるときに、enterPictureInPictureMode(android.app.PictureInPictureParams) または setPictureInPictureParams(android.app.PictureInPictureParams) を使用してパラメータを渡します。注意してください。getMaxNumPictureInPictureActions() を超えて追加しようとすると、最大数のみが追加されます。
PiP での動画再生の継続
アクティビティが PiP に切り替わると、システムはアクティビティを一時停止状態にし、アクティビティの onPause() メソッドを呼び出します。PIP モードへの移行中にアクティビティが一時停止された場合、動画の再生は一時停止されず、再生が継続されます。
Android 7.0(API レベル 24)以降では、システムがアクティビティの onStop() と onStart() を呼び出すときに、動画の再生を一時停止して再開する必要があります。これにより、onPause() でアプリがピクチャー イン ピクチャー モードになっているかどうかを確認して、明示的に再生を継続する必要がなくなります。
setAutoEnterEnabled フラグを true に設定しておらず、onPause() の実装で再生を一時停止する必要がある場合は、isInPictureInPictureMode() を呼び出して PiP モードを確認し、再生を適切に処理します。次に例を示します。
override fun onPause() {
super.onPause()
// If called while in PiP mode, don't pause playback.
if (isInPictureInPictureMode) {
// Continue playback.
} else {
// Use existing playback logic for paused activity behavior.
}
}
アクティビティが PiP モードから全画面モードに戻ると、システムはアクティビティを再開し、onResume() メソッドを呼び出します。
PiP に単一の再生アクティビティを使用する
アプリでは、動画再生アクティビティが PiP モードになっているときに、ユーザーがメイン画面でコンテンツをブラウジングして新しい動画を選択することがあります。ユーザーを混乱させる可能性がある新しいアクティビティを起動するのではなく、既存の再生アクティビティで新しい動画を全画面モードで再生します。
動画再生リクエストに単一のアクティビティが使用され、必要に応じて PiP モードに切り替えられるようにするには、マニフェストでアクティビティの android:launchMode を singleTask に設定します。
<activity android:name="VideoActivity"
...
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
...
アクティビティで onNewIntent() をオーバーライドし、新しい動画を処理します。必要に応じて、既存の動画の再生を停止します。
カメラアプリで PiP をサポートする
カメラアプリで PiP を有効にするには、onPause() が呼び出されたときにカメラを閉じないようにして、PiP モードでカメラがアクティブな状態を維持するようにします。
override fun onPause() {
super.onPause()
// Don't close the camera if the app is entering PiP mode
if (!isInPictureInPictureMode) {
closeCamera()
}
}
他のユースケースと同様に、不要な UI 要素(コントロールやオーバーレイなど)を非表示にし、カメラを制御するカスタム アクション(録画の停止やカメラの切り替えなど)を追加します。
スムーズな遷移のために sourceRectHint を計算する
スムーズな開始アニメーションを実現するには、カメラのビューファインダーの正確な画面座標を含む正確な sourceRectHint を指定することが不可欠です。次のように getGlobalVisibleRect() を使用して、プレビュー ビューから境界を取得できます。
val previewView = findViewById<View>(R.id.preview_view)
val globalRect = Rect()
// Ensure the view is laid out before calling getGlobalVisibleRect() to get valid screen coordinates.
previewView.getGlobalVisibleRect(globalRect)
val params = PictureInPictureParams.Builder()
.setSourceRectHint(globalRect)
.build()
setPictureInPictureParams(params)
その他の考慮事項
RAM が少ないデバイスでは、PIP が無効になっている場合があります。アプリで PIP を使用する前に、hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) を呼び出して PIP が使用可能であることを確認してください。
PIP は、フルスクリーン動画を再生するアクティビティを対象としています。アクティビティを PiP モードに切り替えるときは、動画コンテンツ以外のものは表示しないようにします。PIP 中の UI の処理で説明されているように、アクティビティが PIP モードに切り替わるタイミングを追跡し、UI 要素を非表示にします。
アクティビティが PIP モードのとき、デフォルトでは入力フォーカスを取得しません。PIP モードのときに入力イベントを受信するには、MediaSession.setCallback() を使用します。setCallback() の使用方法について詳しくは、「この曲なに?」カードを表示するをご覧ください。
アプリが PiP モードの場合、PiP ウィンドウでの動画再生が、音楽プレーヤー アプリや音声検索アプリなどの別のアプリの音声に干渉する可能性があります。これを回避するには、動画の再生を開始するときに音声フォーカスをリクエストし、音声フォーカスの管理で説明されているように、音声フォーカスの変更通知を処理します。PiP モードで音声フォーカスが失われたという通知を受け取った場合は、動画の再生を一時停止または停止します。
アプリが PiP に入ろうとしているとき、ピクチャー イン ピクチャーに入るのは最上位のアクティビティのみです。マルチウィンドウ デバイスなどの一部の状況では、基盤となるアクティビティが表示され、PiP アクティビティとともに再び表示される可能性があります。このケースは、基盤となるアクティビティが onResume() または onPause() コールバックを受け取る場合を含め、適切に処理する必要があります。ユーザーがアクティビティを操作する可能性もあります。たとえば、動画リスト アクティビティが表示され、再生中の動画アクティビティがピクチャー イン ピクチャー モードになっている場合、ユーザーがリストから新しい動画を選択すると、それに応じてピクチャー イン ピクチャー アクティビティが更新されます。
他のサンプルコード
Kotlin で記述されたサンプルアプリをダウンロードするには、Android PictureInPicture Sample(Kotlin)をご覧ください。