وابستگی را اضافه کنید
کتابخانه Media3 شامل یک ماژول رابط کاربری مبتنی بر Jetpack Compose است. برای استفاده از آن، وابستگی زیر را اضافه کنید:
کاتلین
implementation("androidx.media3:media3-ui-compose:1.8.0")
گرووی
implementation "androidx.media3:media3-ui-compose:1.8.0"
ما اکیداً شما را تشویق میکنیم که برنامه خود را به روش Compose-first توسعه دهید یا از استفاده از Views مهاجرت کنید .
برنامه آزمایشی کاملاً آهنگسازی
اگرچه کتابخانه media3-ui-compose شامل Composableهای آماده (مانند دکمهها، نشانگرها، تصاویر یا دیالوگها) نمیشود، اما میتوانید یک برنامه آزمایشی (دمو) پیدا کنید که کاملاً با Compose نوشته شده باشد و از هرگونه راهحل قابلیت همکاری مانند قرار دادن PlayerView در AndroidView اجتناب کند. برنامه آزمایشی از کلاسهای نگهدارنده وضعیت UI از ماژول media3-ui-compose استفاده میکند و از کتابخانه Compose Material3 بهره میبرد.
دارندگان وضعیت UI
برای درک بهتر نحوه استفاده از انعطافپذیری نگهدارندههای وضعیت رابط کاربری در مقابل composableها، نحوه مدیریت وضعیت توسط Compose را مطالعه کنید.
دارندگان حالت دکمه
برای برخی از حالتهای رابط کاربری، فرض میکنیم که به احتمال زیاد توسط Composableهای دکمهمانند مصرف میشوند.
| ایالت | ایالت را به خاطر بسپار | نوع |
|---|---|---|
PlayPauseButtonState | rememberPlayPauseButtonState | ۲-تغییر وضعیت |
PreviousButtonState | rememberPreviousButtonState | ثابت |
NextButtonState | rememberNextButtonState | ثابت |
RepeatButtonState | rememberRepeatButtonState | ۳-تغییر وضعیت |
ShuffleButtonState | rememberShuffleButtonState | ۲-تغییر وضعیت |
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 state است.
سپس میتوانید دکمهها را در طرحبندی دلخواه خود ترکیب و مطابقت دهید:
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
دارندگان وضعیت خروجی بصری
PresentationState اطلاعاتی را در مورد اینکه چه زمانی خروجی ویدیو در PlayerSurface میتواند نمایش داده شود یا باید توسط یک عنصر رابط کاربری placeholder پوشانده شود، نگه میدارد.
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 برای مقیاسبندی سطح به نسبت ابعاد مورد نظر (برای انواع بیشتر به اسناد ContentScale مراجعه کنید) و presentationState.coverSurface برای دانستن اینکه چه زمانی زمانبندی برای نمایش سطح مناسب نیست، استفاده کنیم. در این حالت، میتوانید یک شاتر مات را روی سطح قرار دهید که وقتی سطح آماده شد، ناپدید میشود.
فلوز کجا هستند؟
بسیاری از توسعهدهندگان اندروید با استفاده از اشیاء Kotlin Flow برای جمعآوری دادههای رابط کاربری که دائماً در حال تغییر هستند، آشنا هستند. برای مثال، ممکن است به دنبال جریان Player.isPlaying باشید که بتوانید آن را به شیوهای آگاه از چرخه حیات collect . یا چیزی مانند Player.eventsFlow که Flow<Player.Events> را در اختیار شما قرار میدهد که میتوانید آن را به دلخواه filter .
با این حال، استفاده از جریانها برای وضعیت رابط کاربری Player دارای معایبی است. یکی از نگرانیهای اصلی، ماهیت ناهمزمان انتقال دادهها است. ما میخواهیم تا حد امکان تأخیر کمی بین یک Player.Event و مصرف آن در سمت رابط کاربری تضمین کنیم و از نمایش عناصر رابط کاربری که با Player همگام نیستند، اجتناب کنیم.
نکات دیگر عبارتند از:
- یک جریان با تمام
Player.Eventsبه یک اصل مسئولیت واحد پایبند نیست، هر مصرفکننده باید رویدادهای مربوطه را فیلتر کند. - ایجاد یک جریان برای هر
Player.Eventمستلزم آن است که شما آنها را (باcombine) برای هر عنصر رابط کاربری ترکیب کنید. یک نگاشت چند به چند بین یک Player.Event و تغییر یک عنصر رابط کاربری وجود دارد. استفاده ازcombineمیتواند رابط کاربری را به حالتهای بالقوه غیرقانونی سوق دهد.
ایجاد حالتهای رابط کاربری سفارشی
اگر حالتهای رابط کاربری موجود نیازهای شما را برآورده نمیکنند، میتوانید حالتهای رابط کاربری سفارشی اضافه کنید. برای کپی کردن الگو، کد منبع حالت موجود را بررسی کنید. یک کلاس نگهدارنده حالت رابط کاربری معمولی موارد زیر را انجام میدهد:
- یک
Playerجذب میکند. - با استفاده از کوروتینها در
Playerمشترک میشود. برای جزئیات بیشتر بهPlayer.listenمراجعه کنید. - با بهروزرسانی وضعیت داخلی خود، به
Player.Eventsخاص پاسخ میدهد. - دستورات منطق تجاری را که به یک بهروزرسانی مناسب
Playerتبدیل میشوند، بپذیرید. - میتواند در چندین مکان در سراسر درخت رابط کاربری ایجاد شود و همیشه یک نمای ثابت از وضعیت بازیکن را حفظ میکند.
- فیلدهای 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 از حالتهای مختلف رابط کاربری به توسعهدهنده نهایی کمک میکند تا خود را درگیر یادگیری در مورد Player.Events نکند.