子母畫面 (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 程式庫會提供更多子母畫面功能,讓採用者輕鬆存取其他功能。
遷移工作流程
找出應用程式的用途類別和舊版子母畫面邏輯:
類別:影片播放、導航或視訊通話。
要識別的舊版子母畫面邏輯:
onUserLeaveHintsetAutoEnterEnabledonPictureInPictureModeChangedonPictureInPictureUiStateChangedsetPictureInPictureParams。
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 程式庫處理。
主要差異:
- 應用程式端不需要區分自動進入和舊版進入。
- 整合回呼介面。
- 新的
PictureInPictureParams建構工具,可回溯相容。
視訊通話
應用程式會通知程式庫通話狀態為有效或無效,並設定顯示比例。
主要差異:
- 應用程式端不需要區分自動進入和舊版進入。
- 整合回呼介面。
- 新的
PictureInPictureParams建構工具,可提供回溯相容性。 - 視訊通話的標準化動作圖示。
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() } } } }