Thư viện media3-ui-compose cung cấp các thành phần cơ bản để tạo giao diện người dùng đa phương tiện trong Jetpack Compose. Thư viện này được thiết kế cho những 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 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 Material3 và các thành phần Compose tuỳ chỉnh
Thư viện media3-ui-compose-material3 được thiết kế để có tính 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 hãy thay thế một thành phần duy nhất bằng một thành phần triển khai tuỳ chỉnh khi cần có thêm quyền kiểm soát. Đâ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 PreviousButton và NextButton tiêu chuẩn trong thư viện Material3, nhưng bạn cần một PlayPauseButton hoàn toàn tuỳ chỉnh. Bạn có thể đạt được điều này bằng cách sử dụng PlayPauseButton trong thư viện media3-ui-compose cốt lõi và đặt nó cùng với 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 được tạo sẵn cho các chế độ điều khiển thường dùng của trình phát. Sau đây là một số thành phần mà bạn có thể sử dụng trực tiếp trong ứng dụng của mình:
| Thành phần | Mô tả |
|---|---|
PlayPauseButton |
Một vùng chứa trạng thái cho nút chuyển đổi giữa trạng thái phát và tạm dừng. |
SeekBackButton |
Vùng chứa trạng thái cho một nút tìm kiếm ngược theo mức tăng được xác định. |
SeekForwardButton |
Vùng chứa trạng thái cho một nút tìm kiếm tiến theo một mức tăng xác định. |
NextButton |
Vùng chứa trạng thái cho nút tìm kiếm 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 giữa các chế độ lặp lại. |
ShuffleButton |
Vùng chứa trạng thái cho nút bật/tắt chế độ phát ngẫu nhiên. |
MuteButton |
Vùng chứa trạng thái cho nút tắt và bật tiếng trình phát. |
TimeText |
Một vùng chứa trạng thái cho thành phần kết hợp hiển thị tiến trình của người chơi. |
ContentFrame |
Một nền tảng để 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 |
Bề mặt thô bao bọc SurfaceView và TextureView trong AndroidView. |
Phần tử giữ trạng thái giao diện người dùng
Nếu không có thành phần tạo khung nào đáp ứng được nhu cầu, bạn cũng có thể sử dụng trực tiếp các đối tượng trạng thái. Nhìn chung, bạn nên sử dụng các phương thức remember tương ứng để duy trì 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 các thành phần kết hợp giống nút sử dụng.
| Trạng thái | remember*State | Loại |
|---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2-Toggle |
PreviousButtonState |
rememberPreviousButtonState |
Hằng số |
NextButtonState |
rememberNextButtonState |
Hằng số |
RepeatButtonState |
rememberRepeatButtonState |
3-Toggle |
ShuffleButtonState |
rememberShuffleButtonState |
2-Toggle |
PlaybackSpeedState |
rememberPlaybackSpeedState |
Trình đơn hoặc N-Toggle |
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
PresentationState lưu giữ thông tin về thời điểm có thể hiển thị đầu ra video trong PlayerSurface hoặc thời điểm đầu ra video phải được che 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 bề mặt 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.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() } }
Ở đây, chúng ta có thể sử dụng cả presentationState.videoSizeDp để điều chỉnh tỷ lệ Surface theo tỷ lệ khung hình đã chọn (xem tài liệu ContentScale để 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ờ lên trên bề mặt. Màn trập này sẽ biến mất khi bề mặt sẵn sàng. ContentFrame cho phép bạn tuỳ chỉnh màn trập dưới dạng một lambda theo sau, nhưng theo mặc định, màn trập sẽ là @Composable Box màu đen, lấp đầy kích thước của vùng chứa mẹ.
Flows nằm ở đâu?
Nhiều nhà phát triển Android đã quen với việc sử dụng các đối tượng Kotlin Flow để thu thập dữ liệu giao diện người dùng luôn thay đổi. Ví dụ: bạn có thể 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 các luồng 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à tính chất không đồng bộ của việc chuyển dữ liệu. Chúng ta muốn đạt được độ trễ thấp nhất có thể giữa một Player.Event và mức tiêu thụ của nó ở phía giao diện người dùng, tránh hiển thị các phần tử giao diện người dùng không đồng bộ với Player.
Các điểm khác bao gồm:
- Luồng có tất cả
Player.Eventssẽ không tuân thủ nguyên tắc 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 luồng cho mỗi
Player.Eventsẽ yêu cầu bạn kết hợp các luồng đó (vớicombine) cho từng phần tử giao diện người dùng. Có mối quan hệ ánh xạ nhiều-nhiều giữa Player.Event và một thay đổi về thành phần trê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 chuyển sang 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. Kiểm tra mã nguồn của trạng thái hiện có để sao chép mẫu. Lớp phần tử giữ trạng thái giao diện người dùng thông thường sẽ làm những việc sau:
- Nhận một
Player. - Đăng ký
Playerbằng coroutine. Hãy xemPlayer.listenđể biết thêm thông tin chi tiết. - Phản hồi một
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 một lệnh cập nhật
Playerthích hợp. - Có thể được tạo ở nhiều nơi trong cây giao diện người dùng và sẽ luôn duy trì một chế độ xem nhất quán về trạng thái của Người chơi.
- Hiển thị các trường
Statecủa Compose mà một Thành phần kết hợp có thể sử dụng để phản hồi các thay đổi một cách linh động. - Đi kèm với hàm
remember*Stateđể ghi nhớ thực thể giữa các thành phần.
Điều gì xảy ra ở hậu trường:
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)
}
}
}
Để phản ứng với Player.Events của riêng bạn, bạn có thể bắt chúng bằng Player.listen. Đây là một suspend fun cho phép bạn tham gia vào thế giới coroutine và lắng nghe Player.Events vô thời hạn. Việc triển khai Media3 cho 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.