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
に設定して、動画アクティビティをマニフェストに登録します。また、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 モードに切り替えます。
override fun onActionClicked(action: Action) { if (action.id.toInt() == R.id.lb_control_picture_in_picture) { activity?.enterPictureInPictureMode() return } }
@Override public void onActionClicked(Action action) { if (action.getId() == R.id.lb_control_picture_in_picture) { getActivity().enterPictureInPictureMode(); return; } ... }
バックグラウンドに移動するのではなく、アクティビティを PIP モードに切り替えるロジックを組み込むこともできます。たとえば、Google マップは、ユーザーがホームボタンまたは最近ボタンを押して別のアプリに移動すると、PIP モードに切り替わります。このようなケースをキャッチするには、次のように onUserLeaveHint()
をオーバーライドします。
override fun onUserLeaveHint() { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode() } }
@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
を作成します。setPictureInPictureParams(PictureInPictureParams.Builder() .setAspectRatio(aspectRatio) .setSourceRectHint(sourceRectHint) .setAutoEnterEnabled(true) .build())
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
を作成します。動画プレーヤーにレイアウト変更リスナーを接続することもおすすめします。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)
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
を更新できます。// 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() ) } }
// 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 ウィンドウで動画以外のコンテンツのサイズを変更すると、ユーザーに不快感を与える視覚的アーティファクトが発生していました。
動画以外のコンテンツのシームレスなサイズ変更を無効にするには、次のようにします。
setPictureInPictureParams(PictureInPictureParams.Builder() .setSeamlessResizeEnabled(false) .build())
setPictureInPictureParams(new PictureInPictureParams.Builder() .setSeamlessResizeEnabled(false) .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 public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) { if (pipState.isTransitioningToPip()) { // Hide UI elements. } }
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { if (isInPictureInPictureMode) { // Unhide UI elements. } }
@Override public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { if (isInPictureInPictureMode) { // Unhide UI elements. } }
無関係な UI 要素(PIP ウィンドウ用)の表示をすばやく切り替えられるため、PIP 開始アニメーションがよりスムーズでちらつきのないものになります。
これらのコールバックをオーバーライドして、アクティビティの UI 要素を再描画します。PIP モードでは、アクティビティを表示するウィンドウが小さいことに留意してください。アプリが PIP モードのため UI 要素が小さくて詳細が見づらいと、ユーザーはアプリの UI 要素を操作できません。動画再生アクティビティでは、UI の数を最小限にすることがユーザー エクスペリエンスの向上につながります。
アプリで PIP のカスタム アクションを提供する必要がある場合は、このページのコントロールを追加するをご覧ください。他の UI 要素は、アクティビティを PIP に切り替える前に削除し、全画面表示に戻すときに復元するようにします。
コントロールを追加する
PIP ウィンドウでは、ユーザーが(モバイル デバイスでウィンドウをタップするか、テレビリモコンでメニューを選択することにより)ウィンドウのメニューを開いたときに、コントロールを表示できます。
アプリにアクティブなメディア セッションがある場合は、「再生」、「一時停止」、「次へ」、「前へ」の各コントロールが表示されます。
PIP モードに入る前に PictureInPictureParams.Builder.setActions()
で PictureInPictureParams
を作成することで、カスタム アクションを明示的に指定し、enterPictureInPictureMode(android.app.PictureInPictureParams)
または setPictureInPictureParams(android.app.PictureInPictureParams)
を使用して PIP モードに入るときにパラメータを渡すこともできます。ここで注意が必要なのは、getMaxNumPictureInPictureActions()
を超える数のアクションを追加しようとしても、最大数のアクションしか追加できないという点です。
PiP で動画再生を続行する
アクティビティが PIP に切り替わると、システムはアクティビティを一時停止状態にし、アクティビティの onPause()
メソッドを呼び出します。PIP モードへの移行中にアクティビティが一時停止された場合、動画の再生を一時停止せず、再生を続行する必要があります。
Android 7.0 以降では、システムがアクティビティの onStop()
または onStart()
を呼び出したとき、アプリで動画再生の一時停止または再開を行う必要があります。これにより、アプリが PIP モードで onPause()
になっているかを確認したり、明示的に再生を継続したりする必要がなくなります。
setAutoEnterEnabled
フラグを true
に設定しておらず、onPause()
の実装で再生を一時停止する必要がある場合は、isInPictureInPictureMode()
を呼び出して PIP モードかどうかを確認し、再生を適切に処理します。例:
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. } }
@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 モードに切り替えるときは、動画以外のコンテンツを表示しないようにしてください。ピクチャー イン ピクチャー時の UI の処理で説明しているように、アクティビティが PIP モードに入るタイミングをトラッキングして UI 要素を非表示にします。
アクティビティが PIP モードのとき、デフォルトでは入力フォーカスを取得しません。PIP モードのときに入力イベントを受信するには、MediaSession.setCallback()
を使用します。setCallback()
の使用方法について詳しくは、「この曲なに?」カードを表示するをご覧ください。
アプリが PIP モードの場合、PIP ウィンドウで動画を再生すると、音楽プレーヤー アプリや音声検索アプリなどの別のアプリとの音声干渉が発生する可能性があります。これを回避するには、音声フォーカスの管理で説明されているように、動画の再生を開始するときに音声フォーカスをリクエストし、音声フォーカスの変更通知を処理します。PIP モードのときに音声フォーカス喪失の通知を受け取った場合は、動画の再生を一時停止または停止します。
アプリが PIP を開始しようとすると、トップ アクティビティのみがピクチャー イン ピクチャーに入ります。ある種の状況では(マルチウィンドウ デバイスなど)、下位のアクティビティが表示されて PIP アクティビティとともにユーザーに再表示される可能性があります。このようなケース(onResume()
コールバックまたは onPause()
コールバックを取得する下位のアクティビティなど)は、状況に応じて適切に処理する必要があります。また、ユーザーがアクティビティを操作する可能性もあります。たとえば、動画リストのアクティビティを表示状態にして、動画再生のアクティビティを PIP モードにした場合は、ユーザーがリストから新しい動画を選択したら、それに応じて PIP アクティビティを更新する必要があります。
他のサンプルコード
Kotlin で記述されたサンプルアプリをダウンロードするには、Android PictureInPicture サンプル(Kotlin)をご覧ください。