เพิ่มการอ้างอิง
ไลบรารี Media3 มีโมดูล UI ที่อิงตาม Jetpack Compose หากต้องการใช้ ให้เพิ่มการอ้างอิงต่อไปนี้
Kotlin
implementation("androidx.media3:media3-ui-compose:1.8.0")
Groovy
implementation "androidx.media3:media3-ui-compose:1.8.0"
เราขอแนะนำให้คุณพัฒนาแอปในลักษณะ Compose-first หรือย้ายข้อมูลจากการใช้ View
แอปเดโมที่สร้างด้วย Compose ทั้งหมด
แม้ว่าmedia3-ui-composeไลบรารีจะไม่มี Composable ที่พร้อมใช้งาน (เช่น ปุ่ม ตัวบ่งชี้ รูปภาพ หรือกล่องโต้ตอบ) แต่คุณจะเห็นแอปเดโมที่เขียนด้วย Compose ทั้งหมดซึ่งหลีกเลี่ยงโซลูชันการทำงานร่วมกัน
เช่น การห่อ PlayerView ใน AndroidView แอปเดโม
ใช้คลาสที่เก็บสถานะ UI จากโมดูล media3-ui-compose และใช้ไลบรารี Compose Material3
ตัวเก็บสถานะ UI
หากต้องการทำความเข้าใจวิธีใช้ความยืดหยุ่นของตัวเก็บสถานะ UI กับ Composable ให้ดียิ่งขึ้น โปรดอ่านวิธีที่ Compose จัดการสถานะ
ตัวเก็บสถานะปุ่ม
สำหรับสถานะ UI บางอย่าง เราจะถือว่าสถานะเหล่านั้นน่าจะใช้กับ Composable ที่มีลักษณะคล้ายปุ่ม
| รัฐ | remember*State | ประเภท |
|---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2-Toggle |
PreviousButtonState |
rememberPreviousButtonState |
ค่าคงที่ |
NextButtonState |
rememberNextButtonState |
ค่าคงที่ |
RepeatButtonState |
rememberRepeatButtonState |
3-สลับ |
ShuffleButtonState |
rememberShuffleButtonState |
2-Toggle |
PlaybackSpeedState |
rememberPlaybackSpeedState |
เมนูหรือ N-Toggle |
ตัวอย่างการใช้งาน 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),
)
}
}
โปรดสังเกตว่า state ไม่มีข้อมูลการจัดธีม เช่น ไอคอนที่จะใช้สำหรับการเล่น
หรือหยุดชั่วคราว หน้าที่ของมันคือการเปลี่ยน Player เป็นสถานะ UI เท่านั้น
จากนั้นคุณจะผสมผสานปุ่มต่างๆ ในเลย์เอาต์ตามที่ต้องการได้
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
ตัวเก็บสถานะเอาต์พุตภาพ
PresentationState มีข้อมูลเกี่ยวกับเวลาที่สามารถแสดงเอาต์พุตวิดีโอใน
PlayerSurface หรือควรครอบคลุมโดยองค์ประกอบ UI ตัวยึดตำแหน่ง
val presentationState = rememberPresentationState(player)
val scaledModifier = Modifier.resizeWithContentScale(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))
}
ในที่นี้ เราสามารถใช้ทั้ง presentationState.videoSizeDp เพื่อปรับขนาด Surface ให้เป็น
สัดส่วนภาพที่ต้องการ (ดูเอกสารContentScale สำหรับประเภทอื่นๆ) และ
presentationState.coverSurface เพื่อทราบว่าเมื่อใดที่ไม่ควรแสดง Surface ในกรณีนี้ คุณสามารถวางชัตเตอร์ทึบแสงไว้ด้านบนของพื้นผิว ซึ่งจะหายไปเมื่อพื้นผิวพร้อมใช้งาน
Flows อยู่ที่ไหน
นักพัฒนาแอป Android หลายคนคุ้นเคยกับการใช้ออบเจ็กต์ Kotlin Flow เพื่อรวบรวมข้อมูล UI ที่เปลี่ยนแปลงอยู่เสมอ
เช่น คุณอาจกำลังมองหาPlayer.isPlayingโฟลว์ที่collectได้ในลักษณะที่รับรู้ถึงวงจรของแอป หรือ
Player.eventsFlow เพื่อให้คุณมี Flow<Player.Events>
ที่filter ได้ตามต้องการ
อย่างไรก็ตาม การใช้โฟลว์สำหรับสถานะ UI ของ Player มีข้อเสียบางประการ ข้อกังวลหลักอย่างหนึ่งคือลักษณะการโอนข้อมูลแบบอะซิงโครนัส เราต้องการให้มั่นใจว่าPlayer.EventและPlayerมีการตอบสนองที่รวดเร็วที่สุดเท่าที่จะเป็นไปได้เมื่อมีการใช้งานในฝั่ง UI รวมถึงหลีกเลี่ยงการแสดงองค์ประกอบ UI ที่ไม่ซิงค์กับPlayer
ประเด็นอื่นๆ ได้แก่
- โฟลว์ที่มี
Player.Eventsทั้งหมดจะไม่เป็นไปตามหลักการความรับผิดชอบเดียว ผู้บริโภคแต่ละรายจะต้องกรองเหตุการณ์ที่เกี่ยวข้องออก - การสร้างโฟลว์สำหรับแต่ละ
Player.Eventจะกำหนดให้คุณต้องรวมเข้าด้วยกัน (ด้วยcombine) สำหรับองค์ประกอบ UI แต่ละรายการ มีการแมปแบบหลายต่อหลายระหว่าง Player.Event กับการเปลี่ยนแปลงองค์ประกอบ UI การต้องใช้combineอาจทำให้ UI อยู่ในสถานะที่อาจผิดกฎหมาย
สร้างสถานะ UI ที่กำหนดเอง
คุณเพิ่มสถานะ UI ที่กำหนดเองได้หากสถานะที่มีอยู่ไม่ตรงกับความต้องการ ดูซอร์สโค้ดของสถานะที่มีอยู่เพื่อคัดลอกรูปแบบ โดยทั่วไปแล้ว คลาสที่เก็บสถานะ UI จะทำสิ่งต่อไปนี้
- ใช้เวลา
Player - สมัครรับข้อมูล
Playerโดยใช้ Coroutine ดูรายละเอียดเพิ่มเติมได้ที่Player.listen - ตอบสนองต่อ
Player.Eventsที่เฉพาะเจาะจงด้วยการอัปเดตสถานะภายใน - ยอมรับคำสั่งตรรกะทางธุรกิจที่จะเปลี่ยนเป็น
Playerการอัปเดตที่เหมาะสม - สร้างได้หลายที่ในโครงสร้าง UI และจะรักษา มุมมองที่สอดคล้องกันของสถานะเพลเยอร์ไว้เสมอ
- แสดงฟิลด์ Compose
Stateที่ Composable ใช้ได้เพื่อตอบสนองต่อการเปลี่ยนแปลงแบบไดนามิก - มาพร้อมฟังก์ชัน
remember*Stateสำหรับจดจำอินสแตนซ์ระหว่าง องค์ประกอบ
สิ่งที่เกิดขึ้นเบื้องหลัง
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)
}
}
}
หากต้องการโต้ตอบกับ Player.Events ของคุณเอง คุณสามารถจับ Player.Events ได้โดยใช้ Player.listen
ซึ่งเป็น suspend fun ที่ช่วยให้คุณเข้าสู่โลกของโครูทีนและ
ฟัง Player.Events ได้อย่างไม่มีกำหนด การใช้งาน Media3 ในสถานะ UI ต่างๆ
ช่วยให้นักพัฒนาแอปปลายทางไม่ต้องกังวลเรื่องการเรียนรู้เกี่ยวกับ
Player.Events