從 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 中的專屬按鈕時,將活動切換至子母畫面模式:
override fun onActionClicked(action: Action) {
if (action.id.toInt() == R.id.lb_control_picture_in_picture) {
activity?.enterPictureInPictureMode()
return
}
}
建議您加入相關邏輯,將活動切換至子母畫面模式,而非進入背景。舉例來說,如果使用者在應用程式進行導航期間按下主畫面按鈕或「最近」按鈕,Google 地圖就會切換至子母畫面模式。您可以覆寫 onUserLeaveHint() 來因應這種情況:
override fun onUserLeaveHint() {
if (iWantToBeInPipModeNow()) {
enterPictureInPictureMode()
}
}
建議:為使用者提供流暢的子母畫面轉換體驗
Android 12 大幅改善了全螢幕和子母畫面視窗之間的動畫轉場效果。強烈建議您實作所有適用的變更。完成後,這些變更會自動調整為適合摺疊式裝置和平板電腦等大螢幕的大小,不需再進行任何其他作業。
如果應用程式未納入適用的更新,子母畫面轉換功能仍可運作,但動畫效果會較不精緻。舉例來說,從全螢幕模式轉換為子母畫面模式時,子母畫面視窗可能會在轉換期間消失,並在轉換完成後重新顯示。
這些異動包括:
- 透過手勢操作,讓轉換至子母畫面模式的過程更加順暢
- 設定適當的
sourceRectHint,以便進入及退出子母畫面模式 - 停用非影片內容的流暢大小調整功能
請參閱 Android Kotlin PictureInPicture 範例,瞭解如何啟用流暢的轉換體驗。
透過手勢操作,讓轉換至子母畫面模式的過程更加順暢
從 Android 12 開始,setAutoEnterEnabled 標記可讓使用者在手勢操作模式下,更順暢地轉換至子母畫面模式中的影片內容,例如從全螢幕向上滑動至主畫面。
如要進行這項變更,請完成下列步驟:
- 使用
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),但僅限必要時。舉例來說,如果目前處於暫停播放狀態,您可能不希望進入子母畫面模式。
設定適當的 sourceRectHint,以便進入及退出子母畫面模式
自 Android 8.0 推出子母畫面功能起,setSourceRectHint 會指出轉換為子母畫面後可見的活動區域,例如影片播放器中的影片檢視區塊邊界。
在 Android 12 中,系統會使用 sourceRectHint,在進入和退出子母畫面模式時,提供更流暢的動畫。
如要正確設定 sourceRectHint,以便進入和退出子母畫面模式,請按照下列步驟操作:
- 使用適當的界限建構
PictureInPictureParams做為sourceRectHint。建議您也為影片播放器附加版面配置變更事件監聽器:
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 標記,可在系統為子母畫面視窗中的非影片內容調整大小時,提供更加流暢的交錯淡出動畫效果。在過去,為子母畫面視窗中的非影片內容調整大小時,可能會產生假影並影響使用體驗。
如何啟用影片內容的流暢大小調整功能:
setPictureInPictureParams(PictureInPictureParams.Builder()
.setSeamlessResizeEnabled(true)
.build())
在子母畫面模式下處理 UI
當活動進入或結束子母畫面 (PiP) 模式時,系統會呼叫 Activity.onPictureInPictureModeChanged() 或 Fragment.onPictureInPictureModeChanged()。
Android 15 導入了多項異動,確保進入子母畫面模式時能更順暢地轉換。如果應用程式的 UI 元素疊加在主要 UI 上,且主要 UI 會進入子母畫面模式,這項功能就非常實用。
開發人員會使用 onPictureInPictureModeChanged() 回呼,定義切換疊加 UI 元素顯示設定的邏輯。當子母畫面進入或退出動畫完成時,就會觸發這個回呼。從 Android 15 開始,PictureInPictureUiState 類別包含新狀態。
有了這個新的 UI 狀態,當子母畫面動畫開始時,指定 Android 15 為目標版本的應用程式會觀察到 Activity#onPictureInPictureUiStateChanged() 回呼叫用 isTransitioningToPip()。當應用程式處於子母畫面模式時,許多 UI 元素與應用程式無關,例如包含建議、即將播放的影片、評分和標題等資訊的檢視畫面或版面配置。應用程式進入子母畫面模式時,請使用 onPictureInPictureUiStateChanged() 回呼隱藏這些 UI 元素。當應用程式從子母畫面視窗進入全螢幕模式時,請使用 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.
}
}
快速切換不相關 UI 元素 (適用於子母畫面視窗) 的顯示設定,有助於確保子母畫面進入動畫更流暢,且不會閃爍。
覆寫這些回呼,重新繪製活動的 UI 元素。請注意,在子母畫面模式下,活動會顯示在小型視窗中。此外,當應用程式處於子母畫面模式時,使用者無法與應用程式的 UI 元素互動,且可能難以查看小型 UI 元素的細節。為了提供最佳使用者體驗,請盡可能讓影片播放活動的 UI 保持精簡。
如果應用程式需要針對子母畫面模式提供自訂動作,請參閱本頁的「新增控制項」。在活動進入子母畫面模式前移除其他 UI 元素,並在活動再次進入全螢幕模式時將元素還原。
新增控制項
當使用者開啟視窗的選單 (在行動裝置上輕觸視窗,或使用電視遙控器選取選單) 時,子母畫面視窗可顯示控制項。
如果應用程式有進行中的媒體工作階段,就會顯示「播放」、「暫停」、「下一個」和「上一個」控制項。
您也可以在進入子母畫面模式前,使用 PictureInPictureParams.Builder.setActions() 建構 PictureInPictureParams,藉此明確指定自訂動作,並在使用 enterPictureInPictureMode(android.app.PictureInPictureParams) 或 setPictureInPictureParams(android.app.PictureInPictureParams) 進入子母畫面模式時傳遞參數。請務必小心。如果您嘗試新增的動作數量超過 getMaxNumPictureInPictureActions(),則只會取得上限值。
在子母畫面模式下繼續播放影片
當活動切換至子母畫面模式時,系統會將活動設為暫停狀態,並呼叫活動的 onPause() 方法。如果活動在轉換至子母畫面模式時暫停,影片不應暫停,而應繼續播放。
在 Android 7.0 (API 級別 24) 以上版本中,當系統呼叫活動的 onStop() 和 onStart() 時,您應該要暫停並繼續播放影片。這樣您就不必檢查應用程式在子母畫面模式下是否處於 onPause() 狀態,也不必明確繼續播放。
如果您尚未將 setAutoEnterEnabled 旗標設為 true,且需要在 onPause() 實作中暫停播放,請呼叫 isInPictureInPictureMode() 檢查子母畫面模式,並妥善處理播放作業。例如:
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.
}
}
當活動從子母畫面模式切換回全螢幕模式時,系統會讓活動繼續進行並呼叫 onResume() 方法。
針對子母畫面使用單一播放活動
在應用程式中,使用者可能會在主要畫面瀏覽內容時選取新影片,而同時也有影片播放活動在子母畫面模式下進行。在這種情況下,請以全螢幕模式在現有播放活動中播放新影片,而不要啟動新活動,避免造成使用者混淆。
為了確保只有一個活動會用於影片播放要求,且該活動會視需要進入或結束子母畫面模式,請在資訊清單中將活動的 android:launchMode 設為 singleTask:
<activity android:name="VideoActivity"
...
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
...
在活動中覆寫 onNewIntent() 並處理新影片,視需要停止任何現有的影片播放作業。
支援相機應用程式的子母畫面
如要為相機應用程式啟用子母畫面模式,請確保相機在子母畫面模式下保持運作,不要在呼叫 onPause() 時關閉相機:
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 較少的裝置上,系統可能會停用子母畫面模式。在應用程式使用子母畫面模式前,請先呼叫 hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE),確認是否能使用該功能。
子母畫面功能適用於播放全螢幕影片的活動。將活動切換至子母畫面模式時,請不要顯示影片內容以外的任何內容。追蹤活動進入子母畫面模式的時機,並依照「在子母畫面模式下處理 UI」一節的說明隱藏 UI 元素。
根據預設,活動處於子母畫面模式時不會取得輸入焦點。如要在子母畫面模式下接收輸入事件,請使用 MediaSession.setCallback()。如要進一步瞭解如何使用 setCallback(),請參閱「顯示『聽聲辨曲』資訊卡」一文。
當應用程式處於子母畫面模式時,子母畫面視窗中的影片播放可能會干擾其他應用程式的音訊,例如音樂播放器應用程式或語音搜尋應用程式。為避免這種情況,請在開始播放影片時要求音訊焦點,並處理音訊焦點變更通知,如「管理音訊焦點」一文所述。在子母畫面模式下,如果系統顯示通知,指出您已失去音訊焦點,請暫停或停止播放影片。
請注意,當應用程式要進入子母畫面模式時,只有上層活動會進入該模式。但現在於某些情況下 (例如在多視窗模式裝置上),下層 Activity 也可能會顯示,並會隨著子母畫面 Activity 再次出現。請根據實際情形處理這種狀況,包括下層活動取得 onResume() 或 onPause() 回呼的情況。使用者也可能會與活動進行互動。舉例來說,假設您顯示了影片清單活動,而影片播放活動處於子母畫面模式。在這種情況下,使用者可能會從清單中選取新影片,這時子母畫面活動應該要根據使用者的互動有所更新。
其他程式碼範例
如要下載以 Kotlin 編寫的範例應用程式,請參閱「Android PictureInPicture 範例 (Kotlin)」。