從 Android 8.0 (API 級別 26) 開始,Android 允許活動在子母畫面 (PiP) 模式下啟動。子母畫面是一種特殊的多視窗模式,用於影片播放、視訊通話和導覽。這個模式可讓使用者將現有的活動視窗固定在畫面角落,同時繼續在主要畫面使用應用程式或瀏覽內容。
子母畫面會利用 Android 7.0 的多視窗 API 提供固定的影片重疊視窗。如要為應用程式新增子母畫面功能,您必須登錄支援子母畫面模式的活動、視需要將活動切換至子母畫面模式,並確認活動處於子母畫面模式時,UI 元素皆為隱藏狀態且影片會繼續播放。
子母畫面視窗會顯示在系統所選的畫面最頂層角落。
搭載 Android 14 (API 級別 34) 以上版本的相容 Android TV OS 裝置也支援子母畫面功能。雖然兩者有許多相似之處,但在電視上使用子母畫面時,仍有其他注意事項。
使用者如何與子母畫面視窗互動
使用者可以將子母畫面視窗拖曳到其他位置。從 Android 12 開始,使用者還可以執行以下操作:
輕觸一下視窗來顯示全螢幕切換鈕、關閉按鈕、設定按鈕,以及應用程式提供的自訂動作 (例如播放控制項)。
輕觸兩下視窗,即可在目前的子母畫面大小和最大/最小的子母畫面大小之間切換。舉例來說,輕觸兩下最大化的視窗會將其縮到最小,反之亦然。
將視窗拖曳到左側或右側邊緣來隱藏視窗。如要取消隱藏視窗,只要輕觸隱藏視窗的可見部分或將視窗拖曳出來即可。
使用雙指撥動方式進行縮放,調整子母畫面視窗的大小。
應用程式可控制目前活動進入子母畫面模式的時機,例如:
如果使用者輕觸主畫面按鈕或向上滑動回到主畫面,活動就能進入子母畫面模式。Google 地圖之所以能在使用者執行其他活動的同時繼續顯示路線指引,就是基於這個機制。
當使用者離開影片來瀏覽其他內容時,應用程式可將影片移至子母畫面模式。
當使用者看完一集影片內容時,應用程式可將影片切換至子母畫面模式。主要畫面會顯示下一集系列影片的宣傳或摘要資訊。
使用者觀看影片時,應用程式可讓使用者將其他內容排入佇列。影片會繼續在子母畫面模式下播放,主要畫面則顯示內容選取活動。
宣告支援子母畫面
根據預設,系統不會自動為應用程式提供子母畫面支援功能。如要讓應用程式支援子母畫面模式,請將 android:supportsPictureInPicture 設為 true,在資訊清單中登錄影片活動。此外,請指出活動會處理版面配置設定異動情形,這樣如果版面配置在子母畫面模式轉換期間有所變更,活動才不會重新啟動。
<activity android:name="VideoActivity"
android:supportsPictureInPicture="true"
android:configChanges=
"screenSize|smallestScreenSize|screenLayout|orientation"
...
Implement PiP with Jetpack
Use the Jetpack Picture-in-Picture library to implement picture-in-picture experience as it streamlines integration and reduces common in-app issues. Refer to our platform sample app to see an example of its usage. However, if you prefer to implement PiP using the platform APIs, refer to the following documentation.
將活動切換至子母畫面模式
從 Android 12 開始,您可以將 setAutoEnterEnabled 旗標設為 true,將活動切換為子母畫面模式。啟用這項設定後,活動會視需要自動切換至子母畫面模式,不必在 onUserLeaveHint 中明確呼叫 enterPictureInPictureMode()。此外,這項功能還能提供更流暢的轉場效果。詳情請參閱「透過手勢操作,讓轉換至子母畫面模式的過程更加順暢」。
如果您指定 Android 11 以下版本,活動必須呼叫 enterPictureInPictureMode() 才能切換至子母畫面模式。舉例來說,以下程式碼會在使用者點選應用程式 UI 中的專屬按鈕時,將活動切換至子母畫面模式:
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; } ... }
建議您加入相關邏輯,將活動切換至子母畫面模式,而非進入背景。舉例來說,如果使用者在應用程式進行導航期間按下主畫面按鈕或「最近」按鈕,Google 地圖就會切換至子母畫面模式。您可以覆寫 onUserLeaveHint() 來因應這種情況:
Kotlin
override fun onUserLeaveHint() { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode() } }
Java
@Override public void onUserLeaveHint () { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode(); } }
建議:為使用者提供完善的子母畫面轉場體驗
Android 12 大幅改善了全螢幕和子母畫面視窗之間的動畫轉場效果。強烈建議您導入所有適用的變更。完成後,這些變更會自動調整大小,以配合摺疊式裝置和平板電腦等大螢幕,不需再進行任何作業。
如果應用程式未加入適用的更新,子母畫面轉場效果仍可運作,但動畫會比較粗糙。舉例來說,從全螢幕模式轉換為子母畫面模式時,子母畫面視窗可能會在轉換期間消失,轉換完成後才會重新顯示。
這些異動包括:
- 透過手勢操作,讓轉換至子母畫面模式的過程更加順暢
- 設定適當的
sourceRectHint,以便進入和退出子母畫面模式 - 停用非影片內容的流暢大小調整功能
如要瞭解如何啟用完善的轉場體驗,請參閱 Android Kotlin PictureInPicture 範例。
透過手勢操作,讓轉換至子母畫面模式的過程更加順暢
從 Android 12 開始,setAutoEnterEnabled 標記可提供更流暢的動畫,讓使用者在手勢操作模式下轉換至子母畫面模式的影片內容,例如從全螢幕向上滑動至主畫面時。
如要進行這項變更,請完成下列步驟:
使用
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),但僅限必要時。舉例來說,如果目前處於暫停播放狀態,最好不要進入子母畫面模式。
設定適當的 sourceRectHint,以便進入和退出子母畫面模式
自 Android 8.0 推出子母畫面功能以來,setSourceRectHint 會指出活動在轉換為子母畫面後顯示的區域,例如影片播放器中的影片檢視區塊界線。
在 Android 12 中,系統會使用 sourceRectHint 實作更流暢的動畫,無論是進入或退出子母畫面模式都適用。
如要正確設定進入和退出子母畫面模式的 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。當系統即將結束子母畫面模式時,活動的檢視區塊階層會呈現其目的地配置 (例如全螢幕)。應用程式可將版面配置變更事件監聽器附加至根檢視區塊或目標檢視區塊 (例如影片播放器檢視區塊),藉此偵測事件並在動畫開始前更新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 標記,可在系統為子母畫面視窗中的非影片內容調整大小時,提供更加流暢的交錯淡出動畫效果。在過去,為子母畫面視窗中的非影片內容調整大小時,可能會產生假影並影響使用體驗。
如何啟用影片內容的流暢大小調整功能:
Kotlin
setPictureInPictureParams(PictureInPictureParams.Builder() .setSeamlessResizeEnabled(true) .build())
Java
setPictureInPictureParams(new PictureInPictureParams.Builder() .setSeamlessResizeEnabled(true) .build());
在子母畫面模式下處理 UI
當活動進入或結束子母畫面 (PiP) 模式時,系統會呼叫 Activity.onPictureInPictureModeChanged() 或 Fragment.onPictureInPictureModeChanged()。
Android 15 導入的變更可確保進入子母畫面模式時,轉換過程更加流暢。如果應用程式在主要 UI 上方疊加 UI 元素,並進入子母畫面模式,這項功能就非常實用。
開發人員會使用 onPictureInPictureModeChanged() 回呼定義邏輯,切換重疊 UI 元素的顯示設定。當進入或退出子母畫面模式的動畫完成時,系統會觸發這個回呼。
從 Android 15 開始,PictureInPictureUiState 類別會包含新狀態。
應用程式以 Android 15 為目標時,只要子母畫面動畫開始,就會觀察到 Activity#onPictureInPictureUiStateChanged() 回呼以 isTransitioningToPip() 叫用。應用程式處於子母畫面模式時,許多 UI 元素都與應用程式無關,例如包含建議、即將播放的影片、評分和名稱等資訊的檢視區塊或版面配置。應用程式進入子母畫面模式時,請使用 onPictureInPictureUiStateChanged() 回呼隱藏這些 UI 元素。當應用程式從子母畫面視窗進入全螢幕模式時,請使用 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. } }
這個快速切換不相關 UI 元素 (適用於子母畫面視窗) 的可見度,有助於確保子母畫面進入動畫更流暢,且不會閃爍。
覆寫這些回呼,重新繪製活動的 UI 元素。請注意,在子母畫面模式下,活動會顯示在小型視窗中。此外,當應用程式處於子母畫面模式時,使用者無法與應用程式的 UI 元素互動,且可能難以查看小型 UI 元素的細節。為了提供最佳使用者體驗,請盡可能讓影片播放活動的 UI 保持精簡。
如果應用程式需要針對子母畫面模式提供自訂動作,請參閱本頁的「新增控制項」。在活動進入子母畫面模式前移除其他 UI 元素,並在活動再次進入全螢幕模式時將元素還原。
新增控制項
當使用者開啟視窗的選單 (在行動裝置上輕觸視窗,或使用電視遙控器選取選單) 時,子母畫面視窗可顯示控制項。
如果應用程式有進行中的媒體工作階段,就會顯示「播放」、「暫停」、「下一個」和「上一個」控制項。
您也可以在進入子母畫面模式前使用 PictureInPictureParams.Builder.setActions() 建構 PictureInPictureParams,藉此明確指定自訂動作,並在使用 enterPictureInPictureMode(android.app.PictureInPictureParams) 或 setPictureInPictureParams(android.app.PictureInPictureParams) 進入子母畫面模式時傳遞參數。請小心,如果您嘗試新增的動作數量超過 getMaxNumPictureInPictureActions(),則只會取得上限值。
在子母畫面模式下繼續播放影片
當活動切換至子母畫面模式時,系統會將活動設為暫停狀態,並呼叫活動的 onPause() 方法。如果活動在轉換至子母畫面模式時處於暫停狀態,影片不應暫停而要繼續播放。
在 Android 7.0 以上版本中,當系統呼叫活動的 onStop() 和 onStart() 時,您應該要暫停並繼續播放影片。這樣您就不必檢查應用程式在子母畫面模式下是否處於 onPause() 狀態,也不必明確繼續播放。
如果您尚未將 setAutoEnterEnabled 旗標設為 true,且需要在 onPause() 實作中暫停播放,請呼叫 isInPictureInPictureMode() 檢查子母畫面模式,並妥善處理播放作業。例如:
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. ... } }
當活動從子母畫面模式切換回全螢幕模式時,系統會讓活動繼續進行並呼叫 onResume() 方法。
針對子母畫面使用單一播放活動
在應用程式中,使用者在主要畫面瀏覽內容時,可能會選取新影片,而同時也有影片播放活動在子母畫面模式下進行。在這種情況下,請以全螢幕模式在現有播放活動中播放新影片,而不要啟動新活動,避免造成使用者混淆。
為了確保只有一個活動會用於影片播放要求,且該活動會視需要進入或結束子母畫面模式,請在資訊清單中將活動的 android:launchMode 設為 singleTask:
<activity android:name="VideoActivity"
...
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
...
在活動中覆寫 onNewIntent() 並處理新影片,視需要停止任何現有的影片播放作業。
最佳做法
在 RAM 較少的裝置上,系統可能會停用子母畫面模式。在應用程式使用子母畫面模式前,請先呼叫 hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE),確認是否能使用該功能。
子母畫面功能適用於播放全螢幕影片的活動。將活動切換至子母畫面模式時,請不要顯示影片以外的內容。追蹤活動進入子母畫面模式的時機,並依照「在子母畫面模式下處理 UI」一節的說明隱藏 UI 元素。
根據預設,活動處於子母畫面模式時不會取得輸入焦點。如要在子母畫面模式下接收輸入事件,請使用 MediaSession.setCallback()。如要進一步瞭解如何使用 setCallback(),請參閱「顯示『聽聲辨曲』資訊卡」。
應用程式處於子母畫面模式時,子母畫面視窗中的影片播放作業可能會干擾其他應用程式的音訊,例如音樂播放器應用程式或語音搜尋應用程式。為避免發生這種情況,請在開始播放影片時要求音訊焦點,並處理音訊焦點變更通知,如「管理音訊焦點」一文所述。在子母畫面模式下,如果系統顯示通知,指出您已失去音訊焦點,請暫停或停止播放影片。
請注意,當應用程式要進入子母畫面模式時,只有上層活動會進入該模式。但現在於某些情況下 (例如在多視窗裝置上),下層活動也可能會顯示,並會隨著子母畫面模式再次出現。請根據實際情形處理這種狀況,包括下層活動取得 onResume() 或 onPause() 回呼的情況。使用者也可能會與活動進行互動。舉例來說,假設您顯示了影片清單活動,而影片播放活動處於子母畫面模式。在這種情況下,使用者可能會從清單中選取新影片,這時子母畫面活動應該要根據使用者的互動有所更新。
其他程式碼範例
如要下載以 Kotlin 編寫的範例應用程式,請參閱「Android PictureInPicture 範例 (Kotlin)」。