Thêm phần phụ thuộc
Thư viện Media3 có một mô-đun giao diện người dùng dựa trên Jetpack Compose. Để sử dụng, hãy thêm phần phụ thuộc sau:
Kotlin
implementation("androidx.media3:media3-ui-compose:1.7.1")
Groovy
implementation "androidx.media3:media3-ui-compose:1.7.1"
Bạn nên phát triển ứng dụng theo cách ưu tiên Compose hoặc di chuyển từ việc sử dụng Khung hiển thị.
Ứng dụng minh hoạ hoàn toàn bằng Compose
Mặc dù thư viện media3-ui-compose
không bao gồm các thành phần kết hợp có sẵn (chẳng hạn như nút, chỉ báo, hình ảnh hoặc hộp thoại), nhưng bạn có thể tìm thấy một ứng dụng minh hoạ được viết hoàn toàn bằng Compose mà không cần đến bất kỳ giải pháp tương tác nào như bao bọc PlayerView
trong AndroidView
. Ứng dụng minh hoạ sử dụng các lớp trình giữ trạng thái giao diện người dùng từ mô-đun media3-ui-compose
và sử dụng thư viện Compose Material3.
Phần tử giữ trạng thái giao diện người dùng
Để 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 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, chúng ta giả định rằng các trạng thái này nhiều khả năng sẽ được dùng bởi các thành phần kết hợp tương tự như nút.
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
:
@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),
)
}
}
Lưu ý rằng state
không có thông tin về việc tạo giao diện, chẳng hạn như biểu tượng dùng để phát hoặc tạm dừng. Trách nhiệm duy nhất của lớp này là chuyển đổi Player
thành trạng thái giao diện người dùng.
Sau đó, bạn có thể kết hợp các nút theo bố cục mà bạn muốn:
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
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ỗ.
val presentationState = rememberPresentationState(player)
val scaledModifier = Modifier.resize(ContentScale.Fit, presentationState.videoSizeDp)
Box(modifier) {
// 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 = player,
surfaceType = SURFACE_TYPE_SURFACE_VIEW,
modifier = scaledModifier,
)
if (presentationState.coverSurface) {
// Cover the surface that is being prepared with a shutter
Box(Modifier.background(Color.Black))
}
Ở đâ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 mong muố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.
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 tôi muốn đảm bảo độ 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:
- Một luồng có tất cả
Player.Events
sẽ 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.Event
sẽ 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ệ nhiều với nhiều giữa Player.Event và một thay đổi về phần tử trên giao diện người dùng. Việc phải sử dụngcombine
có 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. Một lớp phần tử giữ trạng thái giao diện người dùng điển hình sẽ làm những việc sau:
- Nhận một
Player
. - Đăng ký
Player
bằng coroutine. Hãy xemPlayer.listen
để biết thêm thông tin chi tiết. - Phản hồi một
Player.Events
cụ 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 bản cập nhật
Player
thí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 Trình phát.
- Hiển thị các trường
State
củ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.
Những gì diễn 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
.