media3-ui-compose 程式庫提供基礎元件,可在 Jetpack Compose 中建構媒體 UI。如果開發人員需要比 media3-ui-compose-material3 程式庫提供的更多自訂功能,就適合使用這項工具。本頁說明如何使用核心元件和狀態持有者,建立自訂媒體播放器 UI。
混合使用 Material 3 和自訂 Compose 元件
media3-ui-compose-material3 程式庫的設計十分彈性,您可以使用預先建構的元件處理大部分的 UI,但如果需要更多控制權,可以將單一元件換成自訂實作項目。這時,media3-ui-compose 程式庫就派上用場。
舉例來說,假設您想使用 Material3 程式庫中的標準 PreviousButton 和 NextButton,但需要完全自訂的 PlayPauseButton。如要這麼做,請使用核心 media3-ui-compose 程式庫中的 PlayPauseButton,並將其放在預先建構的元件旁。
Row { // Use prebuilt component from the Media3 UI Compose Material3 library PreviousButton(player) // Use the scaffold component from Media3 UI Compose library PlayPauseButton(player) { // `this` is PlayPauseButtonState FilledTonalButton( onClick = { Log.d("PlayPauseButton", "Clicking on play-pause button") this.onClick() }, enabled = this.isEnabled, ) { Icon( imageVector = if (showPlay) Icons.Default.PlayArrow else Icons.Default.Pause, contentDescription = if (showPlay) "Play" else "Pause", ) } } // Use prebuilt component from the Media3 UI Compose Material3 library NextButton(player) }
可用的元件
media3-ui-compose 程式庫提供一組預先建構的可組合函式,可用於常見的播放器控制項。以下是您可以在應用程式中直接使用的元件:
| 元件 | 說明 |
|---|---|
PlayPauseButton |
按鈕的狀態容器,可在播放和暫停之間切換。 |
SeekBackButton |
按鈕的狀態容器,可依定義的增量向後搜尋。 |
SeekForwardButton |
按鈕的狀態容器,可依定義的增量向前搜尋。 |
NextButton |
這個狀態容器適用於跳到下一個媒體項目的按鈕。 |
PreviousButton |
用於跳至上一個媒體項目的按鈕狀態容器。 |
RepeatButton |
按鈕的狀態容器,可循環切換重複模式。 |
ShuffleButton |
切換隨機播放模式的按鈕狀態容器。 |
MuteButton |
按鈕的狀態容器,可將播放器設為靜音或取消靜音。 |
TimeText |
可組合函式的狀態容器,用於顯示玩家進度。 |
ContentFrame |
用於顯示媒體內容的介面,可處理顯示比例管理、大小調整和快門 |
PlayerSurface |
原始介面,會在 AndroidView 中包裝 SurfaceView 和 TextureView。 |
UI 狀態持有物件
如果沒有任何架構元件符合需求,您也可以直接使用狀態物件。一般來說,建議使用對應的 remember 方法,在重組之間保留 UI 外觀。
如要進一步瞭解如何運用 UI 狀態持有者和 Composables 的彈性,請參閱 Compose 管理狀態的方式。
按鈕狀態容器
對於某些 UI 狀態,程式庫會假設這些狀態最有可能由類似按鈕的可組合函式使用。
PlayPauseButtonState 的使用範例:
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), ) }
視覺輸出狀態容器
PresentationState 包含影片輸出內容何時可顯示,或應由預留位置 UI 元素遮蓋的資訊。PlayerSurfaceContentFrame 可組合項會結合處理比例,並負責在尚未準備就緒的介面上顯示快門。
@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 initialized 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 資料。舉例來說,您可能會尋找可Player.isPlaying以生命週期感知方式collect的流程。或是類似 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(COMMAND_ACTION_A)) private set var someFieldValue by mutableStateOf(someFieldDefault) private set fun onClick() { player.actionA() } suspend fun observe(): Nothing = player.listen { events -> if (events.containsAny(EVENT_B_CHANGED, EVENT_C_CHANGED, EVENT_AVAILABLE_COMMANDS_CHANGED)) { someFieldValue = this.someField isEnabled = this.isCommandAvailable(COMMAND_ACTION_A) } } }
如要對自己的 Player.Events 做出反應,可以使用 Player.listen 擷取這些項目,這是 suspend fun,可讓您進入協同程式世界,並無限期監聽 Player.Events。Media3 實作各種 UI 狀態,可協助開發人員不必擔心學習 Player.Events。