Thêm phần phụ thuộc
Thư viện Media3 bao gồm 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.6.0")
Groovy
implementation "androidx.media3:media3-ui-compose:1.6.0"
Bạn nên phát triển ứng dụng theo hướng ưu tiên Compose hoặc di chuyển từ việc sử dụng Khung hiển thị.
Ứng dụng minh hoạ đầy đủ về 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 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 trong Compose để tránh mọi giải pháp tương tác như gói PlayerView
trong AndroidView
. Ứng dụng minh hoạ sử dụng các lớp chủ thể 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 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, chúng tôi giả định rằng các trạng thái đó 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
:
@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 ý cách state
không có thông tin về giao diện, chẳng hạn như biểu tượ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 trong bố cục theo ý 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 hình ảnh
PresentationState
lưu giữ thông tin về thời điểm đầu ra video trong PlayerSurface
có thể hiển thị hoặc phải được một phần tử giao diện người dùng giữ chỗ che phủ.
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ệ khung hình của Surface theo tỷ lệ khung hình mong muốn (xem tài liệu về 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 ở đâu?
Nhiều nhà phát triển Android đã quen với việc sử dụng các đối tượng Flow
của Kotlin để 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 luồng Player.isPlaying
mà bạn có thể collect
theo cách nhận biết được vòng đời. Hoặc một nội dung 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ố hạn chế. Một trong những mối lo ngại chính là tính chất không đồng bộ của quá trình chuyển dữ liệu. Chúng tôi muốn đảm bảo độ trễ càng nhỏ càng tốt giữa Player.Event
và mức sử dụng của Player.Event
ở 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 luồng có tất cả
Player.Events
sẽ không tuân thủ một 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. - Để tạo luồng cho mỗi
Player.Event
, bạn cầ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 liên kết nhiều với nhiều giữa Player.Event và thay đổi thành phần 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ó thể 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 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 thông thường thực hiện những việc sau:
- Nhận
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ộ củaPlayer.Events
đó. - Chấp nhận các lệnh logic kinh doanh sẽ được chuyển đổi thành bản cập nhật
Player
thí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ì chế độ xem nhất quán về trạng thái của Người chơi.
- Hiển thị các trường
State
của Compose mà Thành phần kết hợp có thể sử dụng để phản hồi linh động 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.
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 mình, bạn có thể phát hiện chúng bằng Player.listen
. Đây là một suspend fun
cho phép bạn bước vào thế giới coroutine và 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 về Player.Events
.