新增依附元件
Media3 程式庫包含以 Jetpack Compose 為基礎的 UI 模組。如要使用這項功能,請新增下列依附元件:
Kotlin
implementation("androidx.media3:media3-ui-compose:1.8.0")
Groovy
implementation "androidx.media3:media3-ui-compose:1.8.0"
強烈建議您以 Compose 為優先開發應用程式,或從使用 Views 遷移。
完整的 Compose 示範應用程式
雖然 media3-ui-compose 程式庫不包含現成的 Composable (例如按鈕、指標、圖片或對話方塊),但您可以找到完全以 Compose 編寫的示範應用程式,避免使用任何互通性解決方案,例如將 PlayerView 包裝在 AndroidView 中。這個範例應用程式會使用 media3-ui-compose 模組中的 UI 狀態持有者類別,並採用 Compose Material3 程式庫。
UI 狀態持有物件
如要進一步瞭解如何運用 UI 狀態持有者和可組合函式的彈性,請參閱「Compose 如何管理狀態」。
按鈕狀態容器
對於某些 UI 狀態,我們假設這些狀態最有可能由類似按鈕的可組合項使用。
PlayPauseButtonState 的使用範例:
@Composable
fun PlayPauseButton(player: Player, modifier: Modifier = Modifier) {
val state = rememberPlayPauseButtonState(player)
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
Icon(
imageVector = if (state.showPlay) Icons.Default.PlayArrow else Icons.Default.Pause,
contentDescription =
if (state.showPlay) stringResource(R.string.playpause_button_play)
else stringResource(R.string.playpause_button_pause),
)
}
}
請注意,state 沒有主題資訊,例如用於播放或暫停的圖示。其唯一責任是將 Player 轉換為 UI 狀態。
接著,您可以在偏好的版面配置中混搭按鈕:
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
視覺輸出狀態容器
PresentationState 包含影片輸出內容在 PlayerSurface 中顯示或應由預留位置 UI 元素遮蓋的相關資訊。ContentFrame 可組合項會結合處理比例,並負責在尚未準備好的介面上顯示快門。
@Composable
fun ContentFrame(
player: Player?,
modifier: Modifier = Modifier,
surfaceType: @SurfaceType Int = SURFACE_TYPE_SURFACE_VIEW,
contentScale: ContentScale = ContentScale.Fit,
keepContentOnReset: Boolean = false,
shutter: @Composable () -> Unit = { Box(Modifier.fillMaxSize().background(Color.Black)) },
) {
val presentationState = rememberPresentationState(player, keepContentOnReset)
val scaledModifier = modifier.resizeWithContentScale(contentScale, presentationState.videoSizeDp)
// Always leave PlayerSurface to be part of the Compose tree because it will be initialised in
// the process. If this composable is guarded by some condition, it might never become visible
// because the Player won't emit the relevant event, e.g. the first frame being ready.
PlayerSurface(player, scaledModifier, surfaceType)
if (presentationState.coverSurface) {
// Cover the surface that is being prepared with a shutter
shutter()
}
}
在這裡,我們可以使用 presentationState.videoSizeDp 將 Surface 縮放至所需長寬比 (如需更多類型,請參閱 ContentScale 文件),並使用 presentationState.coverSurface 瞭解顯示 Surface 的時機是否不適當。在這種情況下,您可以在表面上放置不透明的快門,表面準備就緒時,快門就會消失。ContentFrame
可讓您將快門自訂為尾端 lambda,但根據預設,快門會是填滿父項容器大小的黑色 @Composable Box。
Flows 在哪裡?
許多 Android 開發人員都熟悉使用 Kotlin Flow 物件收集不斷變動的 UI 資料。舉例來說,您可能會尋找可collect以生命週期感知方式Player.isPlaying使用的流程。或是Player.eventsFlow,為您提供可Flow<Player.Events>的filter。
不過,使用 Flow 管理 Player UI 狀態有一些缺點。其中一個主要問題是資料移轉的非同步性質。我們希望盡可能縮短 Player.Event 與 UI 端耗用之間的延遲時間,避免顯示與 Player 不同步的 UI 元素。
其他重點包括:
- 如果流程包含所有
Player.Events,就不會遵守單一責任原則,每個消費者都必須篩除相關事件。 - 如要為每個
Player.Event建立流程,您必須將這些流程與每個 UI 元素合併 (使用combine)。Player.Event 與 UI 元素變更之間存在多對多對應關係。必須使用combine可能會導致 UI 進入潛在的非法狀態。
建立自訂 UI 狀態
如果現有 UI 狀態不符合需求,可以新增自訂 UI 狀態。查看現有狀態的原始碼,複製模式。典型的 UI 狀態容器類別會執行下列操作:
- 以
Player為單位。 - 使用協同程式訂閱
Player。詳情請參閱Player.listen。 - 更新內部狀態,以回應特定
Player.Events。 - 接受將轉換為適當
Player更新的業務邏輯指令。 - 可在 UI 樹狀結構中的多個位置建立,且一律會維持 Player 狀態的一致檢視畫面。
- 公開可組合函式可使用的 Compose
State欄位,以動態回應變更。 - 隨附
remember*State函式,可在組合之間記住執行個體。
幕後到底發生了什麼事?
class SomeButtonState(private val player: Player) {
var isEnabled by mutableStateOf(player.isCommandAvailable(Player.COMMAND_ACTION_A))
private set
var someField by mutableStateOf(someFieldDefault)
private set
fun onClick() {
player.actionA()
}
suspend fun observe() =
player.listen { events ->
if (
events.containsAny(
Player.EVENT_B_CHANGED,
Player.EVENT_C_CHANGED,
Player.EVENT_AVAILABLE_COMMANDS_CHANGED,
)
) {
someField = this.someField
isEnabled = this.isCommandAvailable(Player.COMMAND_ACTION_A)
}
}
}
如要對自己的 Player.Events 做出反應,可以使用 Player.listen 擷取,這是 suspend fun,可讓您進入協同程式世界,並無限期監聽 Player.Events。Media3 實作各種 UI 狀態,可協助開發人員不必費心瞭解 Player.Events。