子母畫面 (PiP) 是一種特殊的多視窗模式,最常用於 影片播放。能讓使用者透過固定顯示的小視窗觀看影片 這個畫面角落的切換鈕 主畫面。
子母畫面會利用 Android 7.0 的多視窗 API 提供固定的影片重疊視窗。如要為應用程式新增子母畫面功能,您必須註冊 活動、視需要將活動切換至子母畫面模式,並確認 UI 元素 在活動處於子母畫面模式時,影片則會繼續播放。
本指南說明如何使用 Compose 影片,在 Compose 中將子母畫面新增至應用程式 。瞭解如何使用 Socialite 應用程式,看看這些相片中的最佳解答 實際做法。
為應用程式設定子母畫面模式
在 AndroidManifest.xml
檔案的活動標記中執行以下操作:
- 新增
supportsPictureInPicture
並將其設為true
,宣告您將在應用程式中使用 PiP。 新增
configChanges
並設為orientation|screenLayout|screenSize|smallestScreenSize
可指定 活動會處理版面配置設定變更。這樣一來,如果版面配置在子母畫面模式轉換期間有所變更,活動就不會重新啟動。 敬上<activity android:name=".SnippetsActivity" android:exported="true" android:supportsPictureInPicture="true" android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize" android:theme="@style/Theme.Snippets">
在 Compose 程式碼中執行以下操作:
- 在
Context
中加入這個額外資訊。您將多次使用這個額外資訊 完整存取活動internal fun Context.findActivity(): ComponentActivity { var context = this while (context is ContextWrapper) { if (context is ComponentActivity) return context context = context.baseContext } throw IllegalStateException("Picture in picture should be called in the context of an Activity") }
為 Android 12 以下版本的假應用程式新增子母畫面
如要在 Android 12 以下版本中新增子母畫面,請使用 addOnUserLeaveHintProvider
。追蹤
請按照下列步驟新增 Android 12 以下版本的子母畫面:
- 新增版本閘門,讓只能在版本 O 前存取這個程式碼。
- 使用具有
Context
的DisposableEffect
做為索引鍵。 - 在
DisposableEffect
中,定義當onUserLeaveHintProvider
是使用 lambda 觸發。在 lambda 中,呼叫findActivity()
上的enterPictureInPictureMode()
並傳入PictureInPictureParams.Builder().build()
。 - 使用
findActivity()
新增addOnUserLeaveHintListener
,並傳入 lambda。 - 在
onDispose
中,使用findActivity()
新增removeOnUserLeaveHintListener
並傳入 lambda。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i("PiP info", "API does not support PiP") }
為 Android 12 後的版本新增子母畫面功能
在 Android 12 以後,PictureInPictureParams.Builder
是以
傳遞至應用程式的影片播放器。
- 建立
modifier
,並對其呼叫onGloballyPositioned
。版面配置 稍後的步驟會用到。 - 為
PictureInPictureParams.Builder()
建立變數。 - 新增
if
陳述式,檢查 SDK 是否為 S 以上版本。如果有,請新增 將setAutoEnterEnabled
設為建構工具,並將其設為true
即可進入子母畫面模式 模式。這比透過enterPictureInPictureMode
提供更流暢的動畫。 - 使用
findActivity()
呼叫setPictureInPictureParams()
。撥號至下列時段的build()
:builder
並傳入。
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(true) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
透過按鈕新增子母畫面
如要透過按一下按鈕進入 PiP 模式,請在 findActivity()
上呼叫 enterPictureInPictureMode()
。
參數已由之前對
PictureInPictureParams.Builder
,因此您不需要設定新參數
。不過,如果您想變更按鈕點擊的任何參數,可以在此設定。
val context = LocalContext.current Button(onClick = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.findActivity().enterPictureInPictureMode( PictureInPictureParams.Builder().build() ) } else { Log.i(PIP_TAG, "API does not support PiP") } }) { Text(text = "Enter PiP mode!") }
在子母畫面模式下處理使用者介面
進入子母畫面模式後,應用程式的整個 UI 都會進入子母畫面視窗,除非您 指定 UI 在子母畫面模式下的外觀。
首先,您需要知道應用程式是否處於子母畫面模式。您可以使用 OnPictureInPictureModeChangedProvider
達成此目標。下列程式碼會指出應用程式是否處於子母畫面模式。
@Composable fun rememberIsInPipMode(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val activity = LocalContext.current.findActivity() var pipMode by remember { mutableStateOf(activity.isInPictureInPictureMode) } DisposableEffect(activity) { val observer = Consumer<PictureInPictureModeChangedInfo> { info -> pipMode = info.isInPictureInPictureMode } activity.addOnPictureInPictureModeChangedListener( observer ) onDispose { activity.removeOnPictureInPictureModeChangedListener(observer) } } return pipMode } else { return false } }
現在,您可以使用 rememberIsInPipMode()
切換要顯示的 UI 元素
應用程式進入子母畫面模式時:
val inPipMode = rememberIsInPipMode() Column(modifier = modifier) { // This text will only show up when the app is not in PiP mode if (!inPipMode) { Text( text = "Picture in Picture", ) } VideoPlayer() }
確認應用程式會在適當的時機進入子母畫面模式
在下列情況下,應用程式不應進入子母畫面模式:
- 影片停止或暫停時。
- 如果您位於與影片播放器不同的應用程式頁面。
如要控制應用程式進入子母畫面模式的時機,請新增追蹤狀態的變數
並透過 mutableStateOf
載入影片播放器的畫面。
根據影片播放狀態切換狀態
如要根據影片播放器是否正在播放來切換狀態,請在影片播放器上新增事件監聽器。根據玩家是否啟用狀態變數 是否正在播放:
player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { shouldEnterPipMode = isPlaying } })
根據播放器是否釋出來切換狀態
當玩家釋放時,請將狀態變數設為 false
:
fun releasePlayer() { shouldEnterPipMode = false }
使用狀態來定義是否已進入子母畫面模式 (Android 12 之前的版本)
- 在新增子母畫面 12 之前的版本時,系統會使用
DisposableEffect
,因此您需要建立rememberUpdatedState
建立一個新變數,並將newValue
設為您的 狀態變數。以確保系統會在DisposableEffect
。 在 lambda 中定義
OnUserLeaveHintListener
時的行為 觸發後,請新增if
陳述式,在呼叫enterPictureInPictureMode()
:val currentShouldEnterPipMode by rememberUpdatedState(newValue = shouldEnterPipMode) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { if (currentShouldEnterPipMode) { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i("PiP info", "API does not support PiP") }
使用狀態來定義是否已進入子母畫面模式 (Android 12 後)
將狀態變數傳遞至 setAutoEnterEnabled
,讓應用程式僅進入
各種時間點的子母畫面模式:
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() // Add autoEnterEnabled for versions S and up if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
使用 setSourceRectHint
實作流暢動畫
setSourceRectHint
API 可建立更流暢的動畫,讓應用程式進入 PiP 模式。在 Android 12 以上版本中,這項功能也能讓結束子母畫面模式的動畫更流暢。
將這個 API 新增至子母畫面建構工具,代表用於存放活動的區域
也能看到
- 只有在狀態定義了
setSourceRectHint()
builder
應用程式應進入子母畫面模式。這可避免在應用程式發生時計算sourceRect
就不需要進入子母畫面模式。 - 如要設定
sourceRect
值,請使用系統提供的layoutCoordinates
使用來自修飾符的onGloballyPositioned
函式 - 對
builder
呼叫setSourceRectHint()
,並傳入sourceRect
變數。
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
使用 setAspectRatio
設定子母畫面視窗的顯示比例
如要設定子母畫面視窗的長寬比,您可以選擇特定的
或使用播放器影片大小的寬度和高度。如果您是
使用 media3 播放器,請確認播放器並非空值,
設定長寬比前,影片大小不等於 VideoSize.UNKNOWN
比例。
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
如果您使用自訂播放器,請設定播放器高度的顯示比例 以及寬度和寬度請注意 在初始化期間調整大小,前提是超出預期程式碼的有效邊界 如果顯示比例可能有誤 您的應用程式就會當機你可能需要新增檢查 和媒體的顯示比例類似 廣告。
新增遠端動作
如要在子母畫面視窗新增控制項 (播放、暫停等),請建立
RemoteAction
。
- 為直播控制項加入常數:
// Constant for broadcast receiver const val ACTION_BROADCAST_CONTROL = "broadcast_control" // Intent extras for broadcast controls from Picture-in-Picture mode. const val EXTRA_CONTROL_TYPE = "control_type" const val EXTRA_CONTROL_PLAY = 1 const val EXTRA_CONTROL_PAUSE = 2
- 為子母畫面視窗中的控制項建立
RemoteActions
清單。 - 接著,新增
BroadcastReceiver
並覆寫onReceive()
來設定 以及各按鈕的動作使用DisposableEffect
註冊 接收方和遠端動作棄置玩家後,請取消註冊 接收器。@RequiresApi(Build.VERSION_CODES.O) @Composable fun PlayerBroadcastReceiver(player: Player?) { val isInPipMode = rememberIsInPipMode() if (!isInPipMode || player == null) { // Broadcast receiver is only used if app is in PiP mode and player is non null return } val context = LocalContext.current DisposableEffect(player) { val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if ((intent == null) || (intent.action != ACTION_BROADCAST_CONTROL)) { return } when (intent.getIntExtra(EXTRA_CONTROL_TYPE, 0)) { EXTRA_CONTROL_PAUSE -> player.pause() EXTRA_CONTROL_PLAY -> player.play() } } } ContextCompat.registerReceiver( context, broadcastReceiver, IntentFilter(ACTION_BROADCAST_CONTROL), ContextCompat.RECEIVER_NOT_EXPORTED ) onDispose { context.unregisterReceiver(broadcastReceiver) } } }
- 將您的遠端動作清單傳遞至
PictureInPictureParams.Builder
:val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() builder.setActions( listOfRemoteActions() ) if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(modifier = pipModifier)
後續步驟
在本指南中,您瞭解了在 Compose 中新增 PiP 的最佳做法,無論是 Android 12 之前或之後皆適用。