Android 8.0(API レベル 26)以降では、アクティビティをピクチャー イン ピクチャー(PIP)モードで起動できます。PIP は特別なタイプのマルチウィンドウ モードで、主に動画の再生に使用されます。ユーザーは、メイン画面でアプリ間を移動したりコンテンツをブラウジングしたりしながら、画面の隅に固定された小さなウィンドウで動画を視聴し続けることができます。
PIP は、Android 7.0 で使用可能になったマルチウィンドウ API を利用して、固定された動画オーバーレイ ウィンドウを提供します。アプリで PIP を使用するには、PIP をサポートするアクティビティを登録し、必要に応じてアクティビティを PIP モードに切り替える必要があります。そして、アクティビティが PIP モードのときには、UI 要素を非表示にして動画の再生を継続するようにします。
PIP ウィンドウは、画面の最上位レイヤの、システムによって選択される隅に表示されます。
ユーザーが PIP ウィンドウを操作する方法
ユーザーは、PIP ウィンドウを別の位置にドラッグできます。Android 12 以降では、次のことも行えます。
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 モードに切り替えることができます。この設定により、アクティビティは必要に応じて自動的に PIP モードに切り替わります。onUserLeaveHint
で enterPictureInPictureMode()
を明示的に呼び出す必要はありません。これには、遷移がよりスムーズになるというメリットもあります。詳しくは、ジェスチャー ナビゲーションから 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(false) .build())
Java
setPictureInPictureParams(new PictureInPictureParams.Builder() .setSeamlessResizeEnabled(false) .build());
PIP 中に UI を処理する
アクティビティが PIP モードを開始または終了すると、Activity.onPictureInPictureModeChanged()
または Fragment.onPictureInPictureModeChanged()
が呼び出されます。
アプリでは、これらのコールバックをオーバーライドして、アクティビティの UI 要素を再描画する必要があります。PIP モードでは、アクティビティを表示するウィンドウが小さいことに留意してください。アプリが PIP モードのため UI 要素が小さくて詳細が見づらいと、ユーザーはアプリの UI 要素を操作できません。動画再生アクティビティでは、UI の数を最小限にすることがユーザー エクスペリエンスの向上につながります。
アプリで PIP のカスタム アクションを提供する必要がある場合は、このページのコントロールの追加をご覧ください。他の UI 要素は、アクティビティを PIP に切り替える前に削除し、全画面表示に戻すときに復元するようにします。
Kotlin
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { if (isInPictureInPictureMode) { // Hide the full-screen UI (controls, etc.) while in PiP mode. } else { // Restore the full-screen UI. } }
Java
@Override public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) { if (isInPictureInPictureMode) { // Hide the full-screen UI (controls, etc.) while in PiP mode. } else { // Restore the full-screen 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 モードのときに、ユーザーがメイン画面でコンテンツをブラウジングするときに新しい動画を選択することがあります。ユーザーの混乱を招く可能性がある新しいアクティビティを起動するのではなく、既存の再生アクティビティで新しい動画を全画面モードで再生します。
動画再生リクエストに単一のアクティビティが使用され、必要に応じて 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 アクティビティを更新する必要があります。
他のサンプルコード
Android で作成されたサンプルアプリをダウンロードするには、Android PictureInPicture Sample をご覧ください。Kotlin で記述されたサンプルアプリをダウンロードするには、Android PictureInPicture サンプル(Kotlin)をご覧ください。