media3-ui-compose ไลบรารีมีคอมโพเนนต์พื้นฐานสำหรับ
สร้าง UI ของสื่อใน Jetpack Compose ออกแบบมาสำหรับนักพัฒนาซอฟต์แวร์ที่ต้องการ
การปรับแต่งมากกว่าที่media3-ui-compose-material3
ไลบรารีมีให้ หน้านี้อธิบายวิธีใช้คอมโพเนนต์หลักและตัวยึดสถานะเพื่อสร้าง UI ของเครื่องเล่นสื่อที่กำหนดเอง
การผสมคอมโพเนนต์ Material3 และ Compose ที่กำหนดเอง
media3-ui-compose-material3ไลบรารีได้รับการออกแบบมาให้มีความยืดหยุ่น คุณสามารถ
ใช้คอมโพเนนต์ที่สร้างไว้ล่วงหน้าสำหรับ UI ส่วนใหญ่ แต่เปลี่ยนคอมโพเนนต์เดียว
เป็นการติดตั้งใช้งานที่กำหนดเองเมื่อต้องการควบคุมมากขึ้น ซึ่งเป็นเวลาที่media3-ui-composeไลบรารีเข้ามามีบทบาท
ตัวอย่างเช่น สมมติว่าคุณต้องการใช้ PreviousButton และ NextButton มาตรฐานจากไลบรารี Material3 แต่คุณต้องการ PlayPauseButton ที่กำหนดเองทั้งหมด คุณทำได้โดยใช้ PlayPauseButton จากไลบรารีหลัก media3-ui-compose
และวางไว้ข้างคอมโพเนนต์ที่สร้างไว้ล่วงหน้า
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) }
คอมโพเนนต์ที่พร้อมใช้งาน
media3-ui-compose ไลบรารีมี Composable ที่สร้างไว้ล่วงหน้าสำหรับ
ตัวควบคุมเพลเยอร์ทั่วไป ต่อไปนี้คือคอมโพเนนต์บางส่วนที่คุณใช้ได้โดยตรงใน
แอป
| ส่วนประกอบ | คำอธิบาย |
|---|---|
PlayPauseButton |
คอนเทนเนอร์สถานะสำหรับปุ่มที่สลับระหว่างเล่นและหยุดชั่วคราว |
SeekBackButton |
คอนเทนเนอร์สถานะสำหรับปุ่มที่กรอถอยหลังตามส่วนเพิ่มที่กำหนด |
SeekForwardButton |
คอนเทนเนอร์สถานะสำหรับปุ่มที่กรอไปข้างหน้าตามส่วนที่เพิ่มขึ้นที่กำหนด |
NextButton |
คอนเทนเนอร์สถานะสำหรับปุ่มที่ไปยังรายการสื่อถัดไป |
PreviousButton |
คอนเทนเนอร์สถานะสำหรับปุ่มที่ไปยังรายการสื่อก่อนหน้า |
RepeatButton |
คอนเทนเนอร์สถานะสำหรับปุ่มที่วนรอบโหมดเล่นซ้ำ |
ShuffleButton |
คอนเทนเนอร์สถานะสำหรับปุ่มที่สลับโหมดสุ่ม |
MuteButton |
คอนเทนเนอร์สถานะสำหรับปุ่มที่ปิดและเปิดเสียงเพลเยอร์ |
TimeText |
คอนเทนเนอร์สถานะสำหรับ Composable ที่แสดงความคืบหน้าของเพลเยอร์ |
ContentFrame |
แพลตฟอร์มสำหรับแสดงเนื้อหาสื่อที่จัดการการจัดการอัตราส่วน การปรับขนาด และชัตเตอร์ |
PlayerSurface |
พื้นผิวดิบที่ห่อหุ้ม SurfaceView และ TextureView ใน AndroidView |
ตัวเก็บสถานะ UI
หากไม่มีคอมโพเนนต์การจัดโครงสร้างใดตรงกับความต้องการ คุณก็ใช้
ออบเจ็กต์สถานะได้โดยตรง โดยทั่วไปแล้ว เราขอแนะนำให้ใช้
rememberเมธอดที่เกี่ยวข้องเพื่อรักษาลักษณะ UI ระหว่างการจัดองค์ประกอบใหม่
หากต้องการทำความเข้าใจวิธีใช้ความยืดหยุ่นของที่เก็บสถานะ UI กับ Composables ให้ดียิ่งขึ้น โปรดอ่านเกี่ยวกับวิธีที่ Compose จัดการสถานะ
ตัวเก็บสถานะปุ่ม
สำหรับสถานะ UI บางอย่าง ไลบรารีจะถือว่าสถานะเหล่านั้นมีแนวโน้มสูงที่จะ ใช้โดยคอมโพเนนต์ที่เขียนได้ด้วย Compose ที่คล้ายปุ่ม
| รัฐ | remember*State | ประเภท |
|---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2-Toggle |
PreviousButtonState |
rememberPreviousButtonState |
ค่าคงที่ |
NextButtonState |
rememberNextButtonState |
ค่าคงที่ |
RepeatButtonState |
rememberRepeatButtonState |
3-Toggle |
ShuffleButtonState |
rememberShuffleButtonState |
2-Toggle |
PlaybackSpeedState |
rememberPlaybackSpeedState |
เมนูหรือ N-Toggle |
ตัวอย่างการใช้งาน 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), ) }
ตัวเก็บสถานะเอาต์พุตภาพ
PresentationState มีข้อมูลสำหรับเวลาที่เอาต์พุตวิดีโอในPlayerSurface สามารถแสดงได้หรือควรครอบคลุมโดยองค์ประกอบ UI ตัวยึดตำแหน่ง
ContentFrame Composable จะรวมการจัดการสัดส่วนภาพเข้ากับการดูแล
การแสดงชัตเตอร์เหนือพื้นผิวที่ยังไม่พร้อม
@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() } }
ในที่นี้ เราสามารถใช้ทั้ง presentationState.videoSizeDp เพื่อปรับขนาด Surface ให้เป็น
สัดส่วนภาพที่เลือก (ดูเอกสารContentScale สำหรับประเภทอื่นๆ) และ
presentationState.coverSurface เพื่อทราบว่าเมื่อใดที่เวลาไม่เหมาะสมที่จะ
แสดง Surface ในกรณีนี้ คุณสามารถวางชัตเตอร์ทึบแสงไว้ด้านบน
ของพื้นผิว ซึ่งจะหายไปเมื่อพื้นผิวพร้อมใช้งาน ContentFrame
ช่วยให้คุณปรับแต่งชัตเตอร์เป็น Lambda ต่อท้ายได้ แต่โดยค่าเริ่มต้นจะเป็น
สีดำ@Composable Boxที่เติมขนาดของคอนเทนเนอร์หลัก
Flows อยู่ที่ไหน
นักพัฒนาแอป Android หลายคนคุ้นเคยกับการใช้ออบเจ็กต์ Kotlin Flow เพื่อรวบรวม
ข้อมูล UI ที่เปลี่ยนแปลงอยู่เสมอ เช่น คุณอาจกำลังมองหาโฟลว์ Player.isPlaying ที่คุณสามารถcollect ได้ในลักษณะที่รับรู้ถึงวงจรของคอมโพเนนต์ หรือPlayer.eventsFlowเพื่อมอบFlow<Player.Events>ให้คุณfilterได้ตามต้องการ
อย่างไรก็ตาม การใช้ Flow สำหรับสถานะ UI ของ Player มีข้อเสียบางประการ ข้อกังวลหลักอย่างหนึ่งคือลักษณะการโอนข้อมูลแบบอะซิงโครนัส เราต้องการให้เกิดความหน่วงน้อยที่สุดระหว่างPlayer.Eventกับการใช้งานในฝั่ง 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.listen
ซึ่งเป็น suspend fun ที่ช่วยให้คุณเข้าสู่โลกของโครูทีนและ
ฟัง Player.Events ได้อย่างไม่มีกำหนด การใช้งาน Media3 ในสถานะ UI ต่างๆ
ช่วยให้นักพัฒนาแอปปลายทางไม่ต้องกังวลเรื่องการเรียนรู้เกี่ยวกับ
Player.Events