子母畫面 (PiP) 支援

從 Android 8.0 (API 級別 26) 開始,Android 允許活動在子母畫面 (PiP) 模式下啟動。子母畫面是一種特殊的多視窗模式,主要用於影片播放。這個模式可讓使用者透過固定在畫面角落的小視窗觀看影片,同時繼續在主要畫面使用應用程式或瀏覽內容。

子母畫面會利用 Android 7.0 的多視窗 API 提供固定的影片重疊視窗。如要為應用程式新增子母畫面功能,您必須登錄支援子母畫面模式的活動、視需要將活動切換至子母畫面模式,並確認活動處於子母畫面模式時,UI 元素皆為隱藏狀態且影片會繼續播放。

子母畫面視窗會顯示在系統所選的畫面最頂層角落。

使用者如何與子母畫面視窗互動

使用者可以將子母畫面視窗拖曳到其他位置。從 Android 12 開始,使用者還可以執行以下操作:

  • 輕觸一下視窗來顯示全螢幕切換鈕、關閉按鈕、設定按鈕,以及應用程式提供的自訂動作 (例如播放控制項)。

  • 輕觸兩下視窗,在目前的子母畫面大小和最大的子母畫面大小之間切換。將視窗拖曳到左側或右側邊緣來隱藏視窗。如要取消隱藏視窗,只要輕觸隱藏視窗的可見部分或將視窗拖曳出來即可。

  • 使用雙指撥動方式進行縮放,調整子母畫面視窗的大小。

應用程式可控制目前活動進入子母畫面模式的時機,例如:

  • 如果使用者在按鈕導覽模式下輕觸主畫面按鈕,或在手勢操作模式下向上滑動回到主畫面,活動就能進入子母畫面模式。Google 地圖之所以能在使用者執行其他活動的同時繼續顯示路線指引,就是基於這個機制。

  • 當使用者離開影片來瀏覽其他內容時,應用程式可將影片移至子母畫面模式。

  • 當使用者看完一集影片內容時,應用程式可將影片切換至子母畫面模式。主要畫面會顯示下一集系列影片的宣傳或摘要資訊。

  • 使用者觀看影片時,應用程式可讓使用者將其他內容排入佇列。影片會繼續在子母畫面模式下播放,主要畫面則顯示內容選取活動。

宣告子母畫面支援功能

根據預設,系統不會自動為應用程式提供子母畫面支援功能。如要讓應用程式支援子母畫面模式,請將 android:supportsPictureInPicture 設為 true,在資訊清單中登錄影片活動。此外,請指出活動會處理版面配置設定異動情形,這樣如果版面配置在子母畫面模式轉換期間有所變更,活動才不會重新啟動。

<activity android:name="VideoActivity"
    android:supportsPictureInPicture="true"
    android:configChanges=
        "screenSize|smallestScreenSize|screenLayout|orientation"
    ...

將活動切換至子母畫面模式

如要進入子母畫面模式,活動必須呼叫 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 開始,您可以使用 setAutoEnterEnabled 標記,讓使用者在手勢操作模式下向上滑動至主畫面時,更順暢地轉換至子母畫面模式。

如何實作這項功能:

  1. 使用 setAutoEnterEnabled 建構 PictureInPictureParams.Builder,如下所示:

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
    
  2. 盡快使用最新的 PictureInPictureParams 呼叫 setPictureInPictureParams。應用程式不應等待 onUserLeaveHint 回呼 (就像在 Android 11 中一樣)。

    舉例來說,應用程式最好在一開始播放時呼叫 setPictureInPictureParams。如果長寬比在後續播放時有所改變,也要呼叫這個項目。

  3. 視需要呼叫 setAutoEnterEnabled(false)。舉例來說,如果目前處於暫停播放狀態,影片應用程式最好不要進入子母畫面模式。

在子母畫面模式下處理 UI

當活動進入或結束子母畫面模式時,系統會呼叫 Activity.onPictureInPictureModeChanged()Fragment.onPictureInPictureModeChanged()

您應該要覆寫這些回呼,以重新繪製活動的 UI 元素。請注意,在子母畫面模式下,活動會顯示在小型視窗中。此外,當活動處於子母畫面模式時,使用者無法與應用程式的 UI 元素互動,且可能難以查看小型 UI 元素的細節。為了提供最佳使用者體驗,請盡可能讓影片播放活動的 UI 保持精簡。

如果應用程式需要針對子母畫面模式提供自訂動作,請參閱這份文件的「新增控制項」一節。在活動進入子母畫面模式前移除其他 UI 元素,並在活動再次進入全螢幕模式時將元素還原:

Kotlin

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean,
                                           newConfig: Configuration) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in picture-in-picture 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 picture-in-picture mode.
    } else {
        // Restore the full-screen UI.
        ...
    }
}

讓結束子母畫面模式時的動畫更流暢

從 Android 12 開始,系統現在會重複使用 SourceRectHint,讓結束子母畫面模式時的動畫更流暢。結束子母畫面模式時,系統會使用目前可用的 sourceRectHint 建立動畫。這可能是用於進入子母畫面模式的原始 Rect,或是由應用程式提供且更新過的 Rect

如要實作這項功能,請按照下列步驟更新應用程式:

  1. 繼續透過 sourceRectHintaspectRatio 建構 PictureInPictureParams,打造流暢的進入動畫。

  2. 視需要在系統啟動結束轉換前更新 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());
        }
    });
    

新增控制項

當使用者開啟視窗的選單 (在行動裝置上輕觸視窗,或使用電視遙控器選取選單) 時,子母畫面視窗可顯示控制項。

如果應用程式有進行中的媒體工作階段,就會顯示「播放」、「暫停」、「下一個」和「上一個」控制項。

您也可以在進入子母畫面模式前使用 PictureInPictureParams.Builder.setActions() 建構 PictureInPictureParams,藉此明確指定自訂動作,並在使用 enterPictureInPictureMode(android.app.PictureInPictureParams)setPictureInPictureParams(android.app.PictureInPictureParams) 進入子母畫面模式時傳遞參數。請注意,如果您嘗試新增的動作數量超過 getMaxNumPictureInPictureActions(),則只會取得上限值。

停用非影片內容的流暢大小調整功能

Android 12 新增了 setSeamlessResizeEnabled 標記,可在系統為子母畫面視窗中的非影片內容調整大小時,提供更加流暢的交錯淡出動畫效果。在過去,為子母畫面視窗中的非影片內容調整大小時,可能會產生假影並影響使用體驗。

根據預設,setSeamlessResizeEnabled 標記是設為 true 以提供回溯相容性。如果是影片內容,請繼續設為 true;如果為非影片內容,請變更為 false

如何停用非影片內容的流暢大小調整功能:

  setPictureInPictureParams(new PictureInPictureParams.Builder()
          .setSeamlessResizeEnabled(false)
          .build());

在子母畫面模式下繼續播放影片

當活動切換至子母畫面模式時,系統會將活動設為暫停狀態,並呼叫活動的 onPause() 方法。如果活動在子母畫面模式下處於暫停狀態,影片不應暫停而要繼續播放。

在 Android 7.0 以上版本中,當系統呼叫活動的 onStop()onStart() 時,您應該要暫停並繼續播放影片。這樣您就不必檢查應用程式在子母畫面模式下是否處於 onPause() 狀態,也不必明確繼續播放。

如果必須在 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() 回呼的情況。使用者也可能會與活動進行互動。舉例來說,假設您顯示了影片清單活動,而影片播放活動處於子母畫面模式。在這種情況下,使用者可能會從清單中選取新影片,這時子母畫面活動應該要根據使用者的互動有所更新。

其他程式碼範例

如要下載以 Android 編寫的範例應用程式,請參閱子母畫面範例。如要下載以 Kotlin 編寫的範例應用程式,請參閱「Android PictureInPicture 範例 (Kotlin)」。