使用 Jetpack 子母畫面程式庫

子母畫面 (PiP) Jetpack 程式庫為 Android 應用程式開發人員提供簡化且穩健的解決方案,方便他們實作 PiP 功能,特別是媒體播放、視訊通訊和導覽應用程式。這個程式庫提供統一的 API,有助於消除樣板程式碼、常見的應用程式內錯誤,並提升子母畫面使用者體驗的整體品質。

PiP Jetpack 程式庫可解決 Android 生態系統中的多項重大挑戰和不一致問題,進而簡化現有的 PiP API:

  • 作業系統碎片化:程式庫會自動處理各種 Android 版本中子母畫面 API 呼叫的差異,例如在 Android 12 之前使用 enterPictureInPictureMode,之後使用 isAutoEnterEnabled,因此開發人員不需要管理版本差異。
  • 不正確的子母畫面參數:提供統一的解決方案,可正確設定子母畫面參數 (例如 setSourceRectHint),在媒體播放期間建立流暢的高品質動畫。
  • 統一的子母畫面狀態回呼:將 onPictureInPictureModeChangedonPictureInPictureUiStateChanged 合併為單一統一的回呼介面 (PictureInPictureDelegate.OnPictureInPictureEventListener),簡化狀態和 UI 管理作業。
  • 減少樣板程式碼:程式庫提供預先定義的 RemoteActions,適用於常見用途 (例如播放控制項和視訊通話動作),可減少重複的樣板程式碼。
  • 確保未來支援:透過 Jetpack 程式庫提供更多子母畫面功能,讓採用者能以最少甚至無需付出任何心力,即可存取額外功能。

遷移工作流程

找出應用程式的應用實例類別和舊版子母畫面邏輯:

類別: 影片播放、導航或視訊通話。

舊版子母畫面邏輯識別:

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams

2. AndroidManifest 設定

請確保進入子母畫面模式的活動在 AndroidManifest.xml 中宣告支援,並提供必要的 configChanges,以免不必要的重新啟動:

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

3. 環境設定

將必要的依附元件新增至 build.gradle

dependencies {
implementation("androidx.core:core:1.18.0")
implementation("androidx.activity:activity:1.13.0")
implementation("androidx.core:core-pip:1.0.0-alpha02") }

請使用最新的 AndroidX 程式庫做為依附元件,並參閱版本頁面瞭解相關資訊。

4. 選取範本並初始化

選擇最符合應用程式用途的實作範本:

  • 導覽和視訊通話BasicPictureInPicture; 通常不支援無縫調整大小,也不需要來源矩形提示。
  • 影片播放VideoPlaybackPictureInPicture; 自動追蹤來源矩形提示的播放器檢視區塊界線,並預設啟用無縫調整大小功能。

如要採用 Jetpack 程式庫,請將現有的自訂子母畫面實作項目替換為 Jetpack 程式庫 API。導入作業的複雜度和成本會因應用程式目前的實作方式而異。

以下各節說明子母畫面的常見用途,以及必要的實作步驟:

應用程式會通知程式庫導覽列的啟用或停用狀態,並設定顯示比例。其餘部分由 Jetpack 程式庫負責。

主要差異:

  1. 應用程式端不必區分自動輸入和舊版輸入。
  2. 整合回呼介面。
  3. 新的 PictureInPictureParams 建構工具,可提供回溯相容性。

視訊通話

應用程式會將通話的有效或無效狀態告知程式庫,並設定長寬比。

主要差異:

  1. 應用程式端不必區分自動輸入和舊版輸入。
  2. 整合回呼介面。
  3. 新的 PictureInPictureParams 建構工具,可提供回溯相容性。
  4. 視訊通話的標準化動作圖示。

5. 程式碼遷移

  • 進入邏輯:setEnabled 取代 API 專屬邏輯,例如 Android 12 以上版本的 setAutoEnterEnabled,或 Android 11 以下版本的 onUserLeaveHint。每當子母畫面資格狀態變更時,請觸發此事件。
  • 回呼:onPictureInPictureModeChanged (版面配置切換) 和 onPictureInPictureUiStateChanged (動畫/狀態) 合併為統一的事件型回呼 onPictureInPictureEvent
  • 動作和參數:每當參數變更時,請使用範本執行個體上的 setActionssetAspectRatio 更新參數。
  • 影片特殊處理:如果是影片應用程式,請使用 setPlayerView 自動更新來源矩形提示,確保轉場效果流暢。` ### 6. 清除所用資源

如果是 VideoPlaybackPictureInPicture,請在 onDisposeonDestroy 中呼叫 close,釋出檢視畫面追蹤器等資源。

參考實作模式

導入範例。

導覽和視訊通話

class NavOrVideoCallJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: BasicPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = BasicPictureInPicture(this)
        // BasicPictureInPicture is ideal for Navigation and Video call use cases.
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTERED -> { /* Toggle to PiP layout */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Toggle to Full-screen layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* Optional: PiP is stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* Optional: PiP is unstashed */ }
        }
    }
}

影片播放

class VideoPlaybackJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: VideoPlaybackPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = VideoPlaybackPictureInPicture(this)
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
            ContentScreen(pictureInPictureImpl)
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> { /* Hide overlays */ }
            PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> { /* Animation finished */ }
            PictureInPictureDelegate.Event.ENTERED -> { /* Switch to PiP layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* PiP stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* PiP unstashed */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Return to full-screen */ }
        }
    }

    @Composable
    fun ContentScreen(pipController: VideoPlaybackPictureInPicture) {
        DisposableEffect(pipController) {
            onDispose {
                pipController.close()
            }
        }
    }
}