使用子母畫面 (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"
    ...

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

從 Android 12 開始,您可以將 setAutoEnterEnabled 標記設為 true,將活動切換為子母畫面模式。透過這項設定,活動會視需要自動切換至子母畫面模式,無須在 onUserLeaveHint 中明確呼叫 enterPictureInPictureMode()。轉換畫面也更加順暢。詳情請參閱「透過手勢操作流暢地轉換至子母畫面模式」。

如果您指定 Android 11 以下版本,活動必須呼叫 enterPictureInPictureMode(),才能切換至子母畫面模式。舉例來說,以下程式碼會在使用者點選應用程式 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;
    }
    ...
}

建議您加入邏輯,將活動切換至子母畫面模式,而非進入背景。舉例來說,如果使用者在應用程式導航時按下主畫面按鈕或「最近使用」按鈕,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 旗標能夠使用手勢導覽功能,在子母畫面模式下以手勢順利轉換至影片內容,例如從全螢幕向上滑動到主畫面時,可提供更流暢的動畫效果。

請完成下列步驟來進行變更,並參考這個範例以備查:

  1. 使用 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());
    
  2. 及早使用最新的 PictureInPictureParams 呼叫 setPictureInPictureParams。應用程式不會等待 onUserLeaveHint 回呼 (就像在 Android 11 中的做法一樣)。

    舉例來說,您可能想要在第一次播放時呼叫 setPictureInPictureParams;如果顯示比例已變更,則在後續播放時呼叫。

  3. 呼叫 setAutoEnterEnabled(false),但只在必要時進行。舉例來說,如果目前的播放狀態處於暫停狀態,則可能不會輸入子母畫面。

設定適當的 sourceRectHint 以進入及離開子母畫面模式

從 Android 8.0 推出子母畫面功能之後,setSourceRectHint 代表進入子母畫面後顯示的活動區域,例如影片播放器中的影片檢視邊界。

在 Android 12 中,系統在進入及退出子母畫面模式時,系統會使用 sourceRectHint 實作更流暢的動畫。

如何正確設定進入及離開子母畫面模式的 sourceRectHint

  1. 使用適當的邊界做為 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);
    
  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());
        }
    });
    
    

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

Android 12 新增了 setSeamlessResizeEnabled 旗標,在子母畫面視窗中為非影片內容調整大小時,能提供更順暢的交錯淡出動畫效果。先前,在子母畫面視窗中調整非影片內容大小可能會導致視覺失真。

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

Kotlin

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

Java

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

在子母畫面模式下處理 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 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.
        ...
    }
}

新增控制選項

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

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

您也可以明確指定自訂動作,在進入子母畫面模式前使用 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() 回呼。使用者也可能會與活動互動。舉例來說,假如您顯示了影片清單活動,但影片播放活動處於子母畫面模式,使用者可能會從清單中選取新影片,而子母畫面活動應據此更新。

其他程式碼範例

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