Thư viện media3-ui-compose cung cấp các thành phần cơ bản để xây dựng giao diện người dùng cho nội dung nghe nhìn trong Jetpack Compose. Thư viện này được thiết kế cho các nhà phát triển cần tuỳ chỉnh nhiều hơn so với những gì mà thư viện media3-ui-compose-material3 cung cấp. Trang này giải thích cách sử dụng các thành phần cốt lõi và phần tử giữ trạng thái để tạo giao diện người dùng trình phát nội dung nghe nhìn tuỳ chỉnh.
Kết hợp các thành phần Material3 và Compose tuỳ chỉnh
Thư viện media3-ui-compose-material3 được thiết kế để linh hoạt. Bạn có thể sử dụng các thành phần dựng sẵn cho hầu hết giao diện người dùng, nhưng thay thế một thành phần bằng một cách triển khai tuỳ chỉnh khi cần kiểm soát nhiều hơn. Đây là lúc thư viện media3-ui-compose phát huy tác dụng.
Ví dụ: giả sử bạn muốn sử dụng tiêu chuẩn
PreviousButton và NextButton từ thư viện
Material3, nhưng bạn cần một PlayPauseButton hoàn toàn tuỳ chỉnh. Bạn có thể
thực hiện việc này bằng cách sử dụng PlayPauseButton từ thư viện media3-ui-compose
cốt lõi và đặt bên cạnh các thành phần dựng sẵn.
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) }
Các thành phần có sẵn
Thư viện media3-ui-compose cung cấp một tập hợp các thành phần kết hợp dựng sẵn cho các nút điều khiển trình phát phổ biến. Dưới đây là một số thành phần bạn có thể sử dụng trực tiếp trong ứng dụng:
| Thành phần | Mô tả |
|---|---|
PlayPauseButton |
Vùng chứa trạng thái cho một nút chuyển đổi giữa phát và tạm dừng. |
SeekBackButton |
Vùng chứa trạng thái cho một nút tua lại theo một mức tăng xác định. |
SeekForwardButton |
Vùng chứa trạng thái cho một nút tua đi theo một mức tăng xác định. |
NextButton |
Vùng chứa trạng thái cho một nút tua đến mục nội dung nghe nhìn tiếp theo. |
PreviousButton |
Vùng chứa trạng thái cho một nút tua đến mục nội dung nghe nhìn trước đó. |
RepeatButton |
Vùng chứa trạng thái cho một nút chuyển đổi qua các chế độ lặp lại. |
ShuffleButton |
Vùng chứa trạng thái cho một nút chuyển đổi chế độ phát ngẫu nhiên. |
MuteButton |
Vùng chứa trạng thái cho một nút tắt và bật tiếng trình phát. |
TimeText |
Vùng chứa trạng thái cho một thành phần kết hợp hiển thị tiến trình của trình phát. |
ContentFrame |
Một surface để hiển thị nội dung nghe nhìn, xử lý việc quản lý tỷ lệ khung hình, đổi kích thước và màn trập |
PlayerSurface |
Surface thô bao bọc SurfaceView và TextureView trong AndroidView. |
Tuỳ chỉnh thành phần kết hợp Player
Khi sử dụng thư viện media3-ui-compose-material3, thành phần kết hợp
Player mang đến trải nghiệm phát nội dung nghe nhìn
hoàn chỉnh. Bạn có thể tuỳ chỉnh bố cục của thành phần này bằng cách cung cấp các thành phần kết hợp của riêng bạn cho
các vùng nội dung, hoặc bạn có thể tận dụng đối tượng PlayerDefaults để
tuỳ chỉnh các phần cụ thể của giao diện người dùng, chẳng hạn như TopControls, CenterControls,
BottomControls và ErrorOverlay.
Ví dụ: bạn có thể sử dụng PlayerDefaults.CenterControls để chỉ ghi đè nút phát/tạm dừng ở giữa trong khi vẫn giữ nguyên các nút điều khiển ở giữa khác.
Bạn cũng có thể chuyển một thành phần kết hợp hoàn toàn tuỳ chỉnh cho một tham số như topControls và hoàn toàn bỏ qua các tham số khác như bottomControls để giữ nguyên giá trị mặc định.
@Composable fun CustomPlayerSlots(player: Player, modifier: Modifier = Modifier) { Player( player = player, modifier = modifier, topControls = { p, visible -> // Fully custom top controls AnimatedVisibility(visible) { Text("My custom title") } }, centerControls = { p, visible -> // Use default CenterControls but override the central button PlayerDefaults.CenterControls( player = p, visible = visible, central = { // A custom play/pause button Material3PlayPauseButton(it, modifier = Modifier.size(64.dp)) }, ) }, // bottomControls are left as default ) }
Phần tử giữ trạng thái giao diện người dùng
Nếu không có thành phần khung nào đáp ứng nhu cầu của bạn, bạn cũng có thể sử dụng trực tiếp các đối tượng trạng thái. Bạn nên sử dụng các phương thức remember tương ứng để giữ nguyên giao diện người dùng giữa các lần kết hợp lại.
Để hiểu rõ hơn về cách bạn có thể sử dụng tính linh hoạt của các phần tử giữ trạng thái giao diện người dùng so với các thành phần kết hợp, hãy đọc về cách Compose quản lý Trạng thái.
Phần tử giữ trạng thái nút
Đối với một số trạng thái giao diện người dùng, thư viện giả định rằng các trạng thái này rất có thể sẽ được sử dụng bởi các thành phần kết hợp giống như nút.
| Trạng thái | remember*State | Loại |
|---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2 trạng thái |
PreviousButtonState |
rememberPreviousButtonState |
Hằng số |
NextButtonState |
rememberNextButtonState |
Hằng số |
RepeatButtonState |
rememberRepeatButtonState |
3 trạng thái |
ShuffleButtonState |
rememberShuffleButtonState |
2 trạng thái |
PlaybackSpeedState |
rememberPlaybackSpeedState |
Trình đơn hoặc N trạng thái |
Ví dụ về cách sử dụng 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), ) }
Phần tử giữ trạng thái đầu ra trực quan
CurrentMediaItemState cung cấp thông tin về
MediaItem hiện đang phát, trong khi PlaylistState hiển thị thông tin về tập hợp MediaItems
trên trình phát. Các phần tử này hữu ích cho việc hiển thị thông tin siêu dữ liệu trong giao diện người dùng tuỳ chỉnh.
ErrorState cung cấp thông tin về trạng thái lỗi hiện tại của trình phát, có thể dùng để hiển thị lớp phủ lỗi.
PresentationState giữ thông tin về thời điểm có thể hiển thị đầu ra video trong
PlayerSurface hoặc nên được che phủ bằng một phần tử giao diện người dùng giữ chỗ.
ContentFrame thành phần kết hợp kết hợp việc xử lý tỷ lệ khung hình với việc hiển thị màn trập trên một surface chưa sẵn sàng.
@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.videoAspectRatio) // 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() } }
Ở đây, chúng ta có thể sử dụng cả presentationState.videoAspectRatio để điều chỉnh tỷ lệ Surface
theo tỷ lệ khung hình đã chọn (xem Tỷ lệ nội dung để biết thêm các loại) và
presentationState.coverSurface để biết thời điểm không phù hợp để
hiển thị Surface. Trong trường hợp này, bạn có thể đặt một màn trập mờ ở trên cùng của surface. Màn trập này sẽ biến mất khi surface sẵn sàng.
ContentFrame cho phép bạn tuỳ chỉnh màn trập dưới dạng lambda cuối, nhưng theo mặc định, màn trập sẽ là một @Composable Box màu đen có kích thước bằng với vùng chứa mẹ.
Flow ở đâu?
Nhiều nhà phát triển Android đã quen với việc sử dụng các đối tượng Flow Kotlin để thu thập dữ liệu giao diện người dùng luôn thay đổi. Ví dụ: bạn có thể đang tìm kiếm flow Player.isPlaying mà bạn có thể collect theo cách nhận biết vòng đời. Hoặc
một thứ gì đó như Player.eventsFlow để cung cấp cho bạn một Flow<Player.Events>
mà bạn có thể filter theo cách bạn muốn.
Tuy nhiên, việc sử dụng flow cho trạng thái giao diện người dùng Player có một số nhược điểm. Một trong những mối lo ngại chính là bản chất không đồng bộ của việc truyền dữ liệu. Chúng tôi muốn đạt được độ trễ càng thấp càng tốt giữa Player.Event và việc sử dụng nó ở phía
giao diện người dùng, tránh hiển thị các thành phần giao diện người dùng không đồng bộ với Player.
Các điểm khác bao gồm:
- Một flow có tất cả
Player.Eventssẽ không tuân thủ nguyên tắc một trách nhiệm duy nhất, mỗi người dùng sẽ phải lọc ra các sự kiện có liên quan. - Việc tạo một flow cho mỗi
Player.Eventsẽ yêu cầu bạn kết hợp chúng (vớicombine) cho từng thành phần giao diện người dùng. Có mối quan hệ ánh xạ nhiều với nhiều giữa Player.Event và sự thay đổi thành phần giao diện người dùng. Việc phải sử dụngcombinecó thể khiến giao diện người dùng rơi vào các trạng thái có khả năng bất hợp pháp.
Tạo trạng thái giao diện người dùng tuỳ chỉnh
Bạn có thể thêm các trạng thái giao diện người dùng tuỳ chỉnh nếu các trạng thái hiện có không đáp ứng nhu cầu của bạn. Hãy xem mã nguồn của trạng thái hiện có để sao chép mẫu. Một lớp phần tử giữ trạng thái giao diện người dùng điển hình sẽ thực hiện những việc sau:
- Nhận đối tượng
Player. - Đăng ký
Playerbằng cách sử dụng coroutine. XemPlayer.listenđể biết thêm chi tiết. - Phản hồi các
Player.Eventscụ thể bằng cách cập nhật trạng thái nội bộ. - Chấp nhận các lệnh logic nghiệp vụ sẽ được chuyển đổi thành bản cập nhật
Playerthích hợp. - Có thể được tạo ở nhiều vị trí trên cây giao diện người dùng và sẽ luôn duy trì một khung hiển thị nhất quán về trạng thái của Trình phát.
- Hiển thị các trường
StateCompose mà một thành phần kết hợp có thể sử dụng để phản hồi động đối với các thay đổi. - Đi kèm với hàm
remember*Stateđể ghi nhớ thực thể giữa các thành phần kết hợp.
Những gì xảy ra ở hậu trường:
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) } } }
Để phản ứng với Player.Events của riêng bạn, bạn có thể nắm bắt chúng bằng cách sử dụng Player.listen
Đây là một suspend fun cho phép bạn tham gia vào thế giới coroutine và
nghe Player.Events vô thời hạn. Việc triển khai Media3 của nhiều trạng thái giao diện người dùng giúp nhà phát triển cuối không phải lo lắng về việc tìm hiểu Player.Events.