子母畫面 (PiP) Jetpack 程式庫為 Android 應用程式開發人員提供簡化且穩健的解決方案,方便他們實作 PiP 功能,特別是媒體播放、視訊通訊和導覽應用程式。這個程式庫提供統一的 API,有助於消除樣板程式碼、常見的應用程式內錯誤,並提升子母畫面使用者體驗的整體品質。
PiP Jetpack 程式庫可解決 Android 生態系統中的多項重大挑戰和不一致問題,進而簡化現有的 PiP API:
- 作業系統碎片化:程式庫會自動處理各種 Android 版本中子母畫面 API 呼叫的差異,例如在 Android 12 之前使用
enterPictureInPictureMode,之後使用isAutoEnterEnabled,因此開發人員不需要管理版本差異。 - 不正確的子母畫面參數:提供統一的解決方案,可正確設定子母畫面參數 (例如
setSourceRectHint),在媒體播放期間建立流暢的高品質動畫。 - 統一的子母畫面狀態回呼:將
onPictureInPictureModeChanged和onPictureInPictureUiStateChanged合併為單一統一的回呼介面 (PictureInPictureDelegate.OnPictureInPictureEventListener),簡化狀態和 UI 管理作業。 - 減少樣板程式碼:程式庫提供預先定義的
RemoteActions,適用於常見用途 (例如播放控制項和視訊通話動作),可減少重複的樣板程式碼。 - 確保未來支援:透過 Jetpack 程式庫提供更多子母畫面功能,讓採用者能以最少甚至無需付出任何心力,即可存取額外功能。
遷移工作流程
找出應用程式的應用實例類別和舊版子母畫面邏輯:
類別: 影片播放、導航或視訊通話。
舊版子母畫面邏輯識別:
onUserLeaveHintsetAutoEnterEnabledonPictureInPictureModeChangedonPictureInPictureUiStateChangedsetPictureInPictureParams。
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 程式庫負責。
主要差異:
- 應用程式端不必區分自動輸入和舊版輸入。
- 整合回呼介面。
- 新的
PictureInPictureParams建構工具,可提供回溯相容性。
視訊通話
應用程式會將通話的有效或無效狀態告知程式庫,並設定長寬比。
主要差異:
- 應用程式端不必區分自動輸入和舊版輸入。
- 整合回呼介面。
- 新的
PictureInPictureParams建構工具,可提供回溯相容性。 - 視訊通話的標準化動作圖示。
5. 程式碼遷移
- 進入邏輯:以
setEnabled取代 API 專屬邏輯,例如 Android 12 以上版本的setAutoEnterEnabled,或 Android 11 以下版本的onUserLeaveHint。每當子母畫面資格狀態變更時,請觸發此事件。 - 回呼:將
onPictureInPictureModeChanged(版面配置切換) 和onPictureInPictureUiStateChanged(動畫/狀態) 合併為統一的事件型回呼onPictureInPictureEvent。 - 動作和參數:每當參數變更時,請使用範本執行個體上的
setActions和setAspectRatio更新參數。 - 影片特殊處理:如果是影片應用程式,請使用
setPlayerView自動更新來源矩形提示,確保轉場效果流暢。` ### 6. 清除所用資源
如果是 VideoPlaybackPictureInPicture,請在 onDispose 或 onDestroy 中呼叫 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() } } } }