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 デバイスでもサポートされています。類似点は多いですが、テレビの PiP を使用する際には、追加の考慮事項があります。
ユーザーが PIP ウィンドウを操作する方法
ユーザーは、PIP ウィンドウを別の位置にドラッグできます。Android 12 以降では、次のことも行えます。
PIP ウィンドウをシングルタップすると、全画面表示への切り替え、閉じるボタン、設定ボタン、アプリが提供するカスタム アクション(再生コントロールなど)が表示されます。
PIP ウィンドウをダブルタップすると、現在の PIP サイズと最大または最小の PIP サイズを切り替えられます。たとえば、最大化されたウィンドウをダブルタップすると最小化され、その逆も同様です。
ウィンドウを左端または右端にドラッグして退避状態にします。ウィンドウの退避を解除するには、退避中のウィンドウの表示されている部分をタップするか、ドラッグして引き出します。
ピンチ操作で PIP ウィンドウのサイズを変更できます。
現在のアクティビティが PIP モードに入るタイミングは、アプリで制御します。次に例を示します。
ユーザーがホームボタンをタップするか、または画面を下から上にスワイプしてホーム画面に移動したら、アクティビティを PIP モードに切り替えますたとえば、Google マップは、ユーザーが別のアクティビティを同時に実行している間、この方法でルートを表示し続けます。
ユーザーが動画から他のコンテンツに移動してブラウジングを開始したら、動画を PIP モードに切り替えます。
ユーザーが見ている動画コンテンツがエピソードの終盤に差し掛かったら、動画を PIP モードに切り替えます。メイン画面には、シリーズの次のエピソードに関する宣伝情報やあらすじを表示します。
ユーザーが動画を見ながら他のコンテンツをキューに追加できるようにします。PIP モードで動画を再生し続けながら、メイン画面にコンテンツ選択アクティビティを表示します。
PiP サポートを宣言する
デフォルトでは、システムはアプリによる PIP の使用を自動的にサポートすることはありません。アプリで PIP をサポートするには、android:supportsPictureInPicture
を true
に設定することにより、動画アクティビティをマニフェストに登録します。また、PIP モードへの遷移中にレイアウト変更が発生した場合でもアクティビティが再起動されないようにするため、アクティビティがレイアウト構成の変更を処理するように指定します。
<activity android:name="VideoActivity"
android:supportsPictureInPicture="true"
android:configChanges=
"screenSize|smallestScreenSize|screenLayout|orientation"
...
アクティビティを PIP に切り替える
Android 12 以降では、setAutoEnterEnabled
フラグを true
に設定することで、アクティビティを PIP モードに切り替えることができます。この設定により、アクティビティは onUserLeaveHint
で enterPictureInPictureMode()
を明示的に呼び出すことなく、必要に応じて自動的に PIP モードに切り替わります。また、これにより、トランジションがはるかにスムーズになるというメリットもあります。詳しくは、ジェスチャー ナビゲーションでの PIP モードへの遷移をよりスムーズにするをご覧ください。
Android 11 以下をターゲットとする場合、アクティビティは enterPictureInPictureMode()
を呼び出して PIP モードに切り替える必要があります。次のコード例では、ユーザーがアプリの UI の専用ボタンをクリックしたときに、アクティビティを PIP モードに切り替えています。
Kotlin
override fun onActionClicked(action: Action) { if (action.id.toInt() == R.id.lb_control_picture_in_picture) { activity?.enterPictureInPictureMode() return } }
Java
@Override public void onActionClicked(Action action) { if (action.getId() == R.id.lb_control_picture_in_picture) { getActivity().enterPictureInPictureMode(); return; } ... }
アクティビティをバックグラウンドに移行する代わりに PIP モードに切り替えるロジックを含めることもできます。たとえば、Google マップは、ユーザーがホームボタンまたは最近ボタンを押して別のアプリに移動すると、PIP モードに切り替わります。このようなケースをキャッチするには、次のように onUserLeaveHint()
をオーバーライドします。
Kotlin
override fun onUserLeaveHint() { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode() } }
Java
@Override public void onUserLeaveHint () { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode(); } }
推奨: ユーザーに洗練された PiP への切り替えエクスペリエンスを提供する
Android 12 では、全画面ウィンドウと PIP ウィンドウ間のアニメーション トランジションに大幅な外観上の改善が加えられました。該当する変更をすべて実装することを強くおすすめします。実装すると、これらの変更は、追加の作業なしで、折りたたみ式デバイスやタブレットなどの大画面に自動的にスケーリングされます。
アプリに該当するアップデートが含まれていない場合でも、PiP の切り替えは機能しますが、アニメーションの完成度は低くなります。たとえば、全画面モードから PIP モードに移行すると、移行中に PIP ウィンドウが消え、移行が完了すると再び表示されることがあります。
これらの変更には、次のものが含まれます。
- ジェスチャー ナビゲーションでの PIP モードへの遷移をよりスムーズにする
- PIP モードの開始と終了に適切な
sourceRectHint
を設定する - 動画以外のコンテンツのシームレスなサイズ変更を無効にする
洗練されたトランジション エクスペリエンスを有効にするためのリファレンスとして、Android Kotlin PictureInPicture サンプルを参照してください。
ジェスチャー ナビゲーションでの PIP モードへの遷移をよりスムーズにする
Android 12 以降では、setAutoEnterEnabled
フラグにより、ジェスチャー ナビゲーションを使用して PIP モードで動画コンテンツに移行する際(全画面表示から上にスワイプしてホーム画面に戻る場合など)のアニメーションがよりスムーズになります。
この変更を行うには、次の手順に沿って操作します。サンプルも参考にしてください。
setAutoEnterEnabled
を使用してPictureInPictureParams.Builder
を作成します。Kotlin
setPictureInPictureParams(PictureInPictureParams.Builder() .setAspectRatio(aspectRatio) .setSourceRectHint(sourceRectHint) .setAutoEnterEnabled(true) .build())
Java
setPictureInPictureParams(new PictureInPictureParams.Builder() .setAspectRatio(aspectRatio) .setSourceRectHint(sourceRectHint) .setAutoEnterEnabled(true) .build());
最新の
PictureInPictureParams
を使って、早い段階でsetPictureInPictureParams
を呼び出します。これにより、アプリがonUserLeaveHint
コールバックを待たないようにします(Android 11 ではコールバックを待っていました)。たとえば、アスペクト比が変更された場合は、最初の再生と以降のすべての再生で
setPictureInPictureParams
を呼び出すことができます。setAutoEnterEnabled(false)
を呼び出しますが、必要な場合に限ります。たとえば、動画アプリではほとんどの場合、現在の再生が一時停止状態の場合に PIP モードに入ることは最適ではありません。
PIP モードの開始と終了に適切な sourceRectHint
を設定
Android 8.0 で PIP が導入されて以来、setSourceRectHint
は、ピクチャー イン ピクチャーへの移行後に表示されるアクティビティの領域(動画プレーヤーの動画ビューの境界など)を示していました。
Android 12 では、システムは sourceRectHint
を使用して、PIP モードの開始時と終了時の両方で、よりスムーズなアニメーションを実装します。
PiP モードの開始と終了のために sourceRectHint
を適切に設定するには:
適切な境界を
sourceRectHint
として使用してPictureInPictureParams
を構築します。動画プレーヤーにレイアウト変更リスナーもアタッチすることをおすすめします。Kotlin
val mOnLayoutChangeListener = OnLayoutChangeListener { v: View?, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int, newLeft: Int, newTop: Int, newRight: Int, newBottom: Int -> val sourceRectHint = Rect() mYourVideoView.getGlobalVisibleRect(sourceRectHint) val builder = PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint) setPictureInPictureParams(builder.build()) } mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
Java
private final View.OnLayoutChangeListener mOnLayoutChangeListener = (v, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight, newBottom) -> { final Rect sourceRectHint = new Rect(); mYourVideoView.getGlobalVisibleRect(sourceRectHint); final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint); setPictureInPictureParams(builder.build()); }; mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener);
必要に応じて、システムが終了遷移を開始する前に
sourceRectHint
を更新します。システムが PIP モードを終了しようとすると、アクティビティのビュー階層が遷移先の構成(全画面など)に配置されます。アプリは、レイアウト変更リスナーをルートビューやターゲット ビュー(動画プレーヤー ビューなど)にアタッチすることで、イベントを検出し、アニメーションの開始前にsourceRectHint
を更新できます。Kotlin
// 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() ) } }
Java
// Listener is called right after the user exits PiP but before animating. playerView.addOnLayoutChangeListener((v, 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. final Rect sourceRectHint = new Rect(); playerView.getGlobalVisibleRect(sourceRectHint); setPictureInPictureParams( new PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint) .build()); } });
動画以外のコンテンツのシームレスなサイズ変更を無効にする
Android 12 では、setSeamlessResizeEnabled
フラグが追加されました。このフラグにより、PIP ウィンドウで動画以外のコンテンツのサイズを変更する際に、よりスムーズなクロスフェード アニメーションを提供できるようになりました。以前は、PIP ウィンドウで動画以外のコンテンツのサイズを変更すると、ユーザーに不快感を与える視覚的アーティファクトが発生していました。
動画コンテンツのシームレスなサイズ変更を有効にするには:
Kotlin
setPictureInPictureParams(PictureInPictureParams.Builder() .setSeamlessResizeEnabled(true) .build())
Java
setPictureInPictureParams(new PictureInPictureParams.Builder() .setSeamlessResizeEnabled(true) .build());
PIP 時に UI を処理する
アクティビティがピクチャー イン ピクチャー(PIP)モードに入るか PIP モードから抜けると、システムは Activity.onPictureInPictureModeChanged()
または Fragment.onPictureInPictureModeChanged()
を呼び出します。
Android 15 では、PIP モードへの移行をさらにスムーズにする変更が導入されています。これは、メイン UI の上に UI 要素がオーバーレイ表示され、そのメイン UI が PiP に移行するアプリにメリットがあります。
デベロッパーは onPictureInPictureModeChanged()
コールバックを使用して、オーバーレイされた UI 要素の表示を切り替えるロジックを定義します。このコールバックは、PiP の開始または終了のアニメーションが完了したときにトリガーされます。Android 15 以降、PictureInPictureUiState
クラスに新しい状態が追加されました。
この新しい UI 状態により、Android 15 をターゲットとするアプリは、PIP アニメーションが開始するとすぐに isTransitioningToPip()
で呼び出される Activity#onPictureInPictureUiStateChanged()
コールバックを監視します。PiP モードでは、アプリに関連しない UI 要素が多数あります。たとえば、候補、次の動画、評価、タイトルなどの情報を含むビューやレイアウトなどです。アプリが PIP モードに入ったら、onPictureInPictureUiStateChanged()
コールバックを使用してこれらの UI 要素を非表示にします。アプリが PIP ウィンドウから全画面モードに移行した場合は、次の例に示すように、onPictureInPictureModeChanged()
コールバックを使用してこれらの要素を再表示します。
Kotlin
override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) { if (pipState.isTransitioningToPip()) { // Hide UI elements. } }
Java
@Override public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) { if (pipState.isTransitioningToPip()) { // Hide UI elements. } }
Kotlin
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { if (isInPictureInPictureMode) { // Unhide UI elements. } }
Java
@Override public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { if (isInPictureInPictureMode) { // Unhide UI elements. } }
PIP ウィンドウの関連性のない UI 要素をすばやく表示 / 非表示に切り替えることで、スムーズでちらつきのない PIP 開始アニメーションを実現できます。
アプリでは、これらのコールバックをオーバーライドして、アクティビティの UI 要素を再描画する必要があります。PIP モードでは、アクティビティを表示するウィンドウが小さいことに留意してください。アプリが 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 以降では、システムがアクティビティの onStop()
または onStart()
を呼び出したとき、アプリで動画再生の一時停止または再開を行う必要があります。これにより、onPause()
でアプリが PIP モードかどうかを確認したり、明示的に再生を続行したりする必要がなくなります。
setAutoEnterEnabled
フラグを true
に設定しておらず、onPause()
の実装で再生を一時停止する必要がある場合は、isInPictureInPictureMode()
を呼び出して PIP モードかどうかを確認し、再生を適切に処理します。次に例を示します。
Kotlin
override fun onPause() { super.onPause() // If called while in PiP mode, do not pause playback. if (isInPictureInPictureMode) { // Continue playback. } else { // Use existing playback logic for paused activity behavior. } }
Java
@Override public void onPause() { // If called while in PiP mode, do not pause playback. if (isInPictureInPictureMode()) { // Continue playback. ... } else { // Use existing playback logic for paused activity behavior. ... } }
アクティビティが PIP モードから全画面モードに戻ると、システムはアクティビティを再開して onResume()
を呼び出します。
PIP に単一の再生アクティビティを使用する
動画再生アクティビティが PIP モードのときに、ユーザーがメイン画面でコンテンツをブラウジングして新しい動画を選択する可能性があります。そのような場合、新しい動画は既存の再生アクティビティを全画面表示モードにして再生するようにします。新しいアクティビティを起動すると、ユーザーを混乱させる可能性があるためです。
動画再生リクエストに対して必ず 1 つのアクティビティが使用され、必要に応じて PIP モードの開始と終了が切り替えられるようにするには、次のように、マニフェストでアクティビティの android:launchMode
を singleTask
に設定します。
<activity android:name="VideoActivity"
...
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
...
アクティビティでは、onNewIntent()
をオーバーライドして新しい動画を処理し、必要に応じて既存の動画再生を停止します。
おすすめの方法
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()
コールバックを取得する下位のアクティビティなど)は、状況に応じて適切に処理する必要があります。また、ユーザーがアクティビティを操作する可能性もあります。たとえば、動画リストのアクティビティを表示状態にして、動画再生のアクティビティを PIP モードにした場合は、ユーザーがリストから新しい動画を選択したら、それに応じて PIP アクティビティを更新する必要があります。
他のサンプルコード
Kotlin で記述されたサンプルアプリをダウンロードするには、Android PictureInPicture サンプル(Kotlin)をご覧ください。