เพิ่มการอ้างอิง
ไลบรารี Media3 มีโมดูล UI ที่อิงตาม Jetpack Compose หากต้องการใช้ ให้เพิ่ม การอ้างอิงต่อไปนี้
Kotlin
implementation("androidx.media3:media3-ui-compose:1.7.1")
Groovy
implementation "androidx.media3:media3-ui-compose:1.7.1"
เราขอแนะนำให้คุณพัฒนาแอปในลักษณะ Compose-first หรือย้ายข้อมูลจากการใช้ View
แอปเดโมที่ใช้ Compose ทั้งหมด
แม้ว่าmedia3-ui-compose
ไลบรารีจะไม่มี Composable ที่พร้อมใช้งาน (เช่น ปุ่ม ตัวบ่งชี้ รูปภาพ หรือกล่องโต้ตอบ) แต่คุณจะเห็นแอปเดโมที่เขียนด้วย Compose ทั้งหมดซึ่งหลีกเลี่ยงโซลูชันการทำงานร่วมกัน
เช่น การห่อ PlayerView
ใน AndroidView
แอปเดโม
ใช้คลาสที่เก็บสถานะ UI จากโมดูล media3-ui-compose
และใช้ไลบรารี Compose Material3
ตัวเก็บสถานะ UI
หากต้องการทำความเข้าใจวิธีใช้ความยืดหยุ่นของที่เก็บสถานะ UI กับ Composable ให้ดียิ่งขึ้น โปรดอ่านวิธีที่ Compose จัดการสถานะ
ตัวเก็บสถานะปุ่ม
สำหรับสถานะ UI บางอย่าง เราจะถือว่าสถานะเหล่านั้นมีแนวโน้มที่จะใช้โดย Composables ที่มีลักษณะคล้ายปุ่มมากที่สุด
รัฐ | 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.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))
}
ในที่นี้ เราสามารถใช้ทั้ง presentationState.videoSizeDp
เพื่อปรับขนาด Surface ให้เป็นสัดส่วนภาพที่ต้องการ (ดูเอกสารContentScale สำหรับประเภทอื่นๆ) และ presentationState.coverSurface
เพื่อดูว่าเมื่อใดที่เวลาไม่เหมาะสมที่จะแสดง Surface ในกรณีนี้ คุณสามารถวางชัตเตอร์ทึบแสงไว้ด้านบน
ของพื้นผิว ซึ่งจะหายไปเมื่อพื้นผิวพร้อมใช้งาน
Flows อยู่ที่ไหน
นักพัฒนาแอป Android หลายคนคุ้นเคยกับการใช้ออบเจ็กต์ Kotlin Flow
เพื่อรวบรวม
ข้อมูล UI ที่เปลี่ยนแปลงอยู่เสมอ เช่น คุณอาจกำลังมองหาโฟลว์ Player.isPlaying
ที่คุณสามารถcollect
ในลักษณะที่รับรู้ถึงวงจรของคอมโพเนนต์ หรือPlayer.eventsFlow
เพื่อมอบFlow<Player.Events>
ให้คุณfilter
ได้ตามต้องการ
อย่างไรก็ตาม การใช้ Flow สำหรับสถานะ UI ของ Player
มีข้อเสียบางประการ ข้อกังวลหลักอย่างหนึ่งคือลักษณะการโอนข้อมูลแบบอะซิงโครนัส เราต้องการให้มั่นใจว่าPlayer.Event
และPlayer
มีการตอบสนองที่รวดเร็วที่สุดเท่าที่จะเป็นไปได้ในฝั่ง UI โดยหลีกเลี่ยงการแสดงองค์ประกอบ UI ที่ไม่ซิงค์กับPlayer
ประเด็นอื่นๆ ได้แก่
- โฟลว์ที่มี
Player.Events
ทั้งหมดจะไม่เป็นไปตามหลักการความรับผิดชอบเดียว ผู้บริโภคแต่ละรายจะต้องกรองเหตุการณ์ที่เกี่ยวข้องออก - การสร้างโฟลว์สำหรับแต่ละ
Player.Event
จะกำหนดให้คุณต้องรวมโฟลว์เหล่านั้น (ด้วยcombine
) สำหรับองค์ประกอบ UI แต่ละรายการ มีการแมปแบบหลายต่อหลายระหว่าง Player.Event กับการเปลี่ยนแปลงองค์ประกอบ UI การต้องใช้combine
อาจทำให้ UI อยู่ในสถานะที่อาจผิดกฎหมาย
สร้างสถานะ UI ที่กำหนดเอง
คุณเพิ่มสถานะ UI ที่กำหนดเองได้หากสถานะที่มีอยู่ไม่ตรงกับความต้องการ ดูซอร์สโค้ดของสถานะที่มีอยู่เพื่อคัดลอกรูปแบบ โดยทั่วไปแล้ว คลาสตัวยึดสถานะ UI จะทำสิ่งต่อไปนี้
- ใช้เวลา
Player
- สมัครรับข้อมูล
Player
โดยใช้โครูทีน ดูรายละเอียดเพิ่มเติมได้ที่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