使用 Jetpack 子母畫面程式庫

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

PiP Jetpack 程式庫可解決 Android 生態系統中的幾項主要挑戰和不一致問題,有助於現有的 PiP API 運作:

  • OS 分割:程式庫會自動處理不同 Android 版本中 PiP API 呼叫的差異,例如在 Android 12 之前使用 enterPictureInPictureMode,之後則使用 isAutoEnterEnabled,因此開發人員不需要管理版本差異。
  • 子母畫面參數不正確:提供統一的解決方案,可正確設定子母畫面參數 (例如 setSourceRectHint),在媒體播放期間建立流暢、高品質的動畫。
  • 統一的子母畫面狀態回呼:將 onPictureInPictureModeChanged 和 onPictureInPictureUiStateChanged 整合至單一統一回呼介面 (PictureInPictureDelegate.OnPictureInPictureEventListener),簡化狀態和 UI 管理。
  • 減少樣板程式碼:程式庫針對常見用途 (例如播放控制項和視訊通話動作) 提供預先定義的 RemoteActions 組合,減少重複的樣板程式碼。
  • 與時俱進:Jetpack 程式庫會提供更多子母畫面功能,讓採用者輕鬆存取其他功能。

遷移工作流程

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

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

要識別的舊版子母畫面邏輯:

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams。

2. AndroidManifest 設定

請確認進入子母畫面的 Activity 在 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. 程式碼遷移

  • 進入邏輯:將 API 專屬邏輯 (例如 Android 12 以上版本的 setAutoEnterEnabled,或 Android 11 以下版本的 onUserLeaveHint) 替換為 setEnabled。每當子母畫面資格狀態變更時,請觸發這個事件。
  • 回呼:將 onPictureInPictureModeChanged (版面配置切換) 和 onPictureInPictureUiStateChanged (動畫/狀態) 整合為統一的事件式回呼 onPictureInPictureEvent。
  • 動作和參數:每當範本執行個體變更時,使用 setActions 和 setAspectRatio 更新參數。

參考導入模式

導入範例。

導航和視訊通話

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()
            }
        }
    }
}