کتابخانه media3-ui-compose اجزای اساسی برای ساخت رابط کاربری رسانه در Jetpack Compose را فراهم میکند. این کتابخانه برای توسعهدهندگانی طراحی شده است که به سفارشیسازی بیشتری نسبت به آنچه کتابخانه media3-ui-compose-material3 ارائه میدهد، نیاز دارند. این صفحه نحوه استفاده از اجزای اصلی و نگهدارندههای حالت را برای ایجاد یک رابط کاربری پخشکننده رسانه سفارشی توضیح میدهد.
ترکیب Material3 و کامپوننتهای سفارشی Compose
کتابخانه media3-ui-compose-material3 به گونهای طراحی شده است که انعطافپذیر باشد. شما میتوانید از کامپوننتهای از پیش ساخته شده برای بیشتر رابط کاربری خود استفاده کنید، اما وقتی به کنترل بیشتری نیاز دارید، یک کامپوننت واحد را برای پیادهسازی سفارشی جایگزین کنید. اینجاست که کتابخانه 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 مجموعهای از کامپوننتهای از پیش ساخته شده را برای کنترلهای رایج پخشکننده ارائه میدهد. در اینجا برخی از کامپوننتهایی که میتوانید مستقیماً در برنامه خود استفاده کنید، آورده شده است:
| کامپوننت | توضیحات |
|---|---|
PlayPauseButton | یک ظرف حالت برای دکمهای که بین پخش و مکث تغییر وضعیت میدهد. |
SeekBackButton | یک ظرف حالت برای دکمهای که با یک افزایش تعریفشده به عقب برمیگردد. |
SeekForwardButton | یک ظرف حالت برای دکمهای که با یک افزایش تعریفشده به جلو حرکت میکند. |
NextButton | یک ظرف حالت برای دکمهای که به دنبال آیتم رسانهای بعدی میگردد. |
PreviousButton | یک ظرف حالت برای دکمهای که به دنبال آیتم رسانه قبلی میگردد. |
RepeatButton | یک ظرف حالت برای دکمهای که حالتهای تکراری را طی میکند. |
ShuffleButton | یک ظرف حالت برای دکمهای که حالت تصادفی را تغییر میدهد. |
MuteButton | یک محفظهی حالت برای دکمهای که پخشکننده را بیصدا و بیصدا میکند. |
TimeText | یک کانتینر حالت برای یک کامپوننت که پیشرفت بازیکن را نمایش میدهد. |
ContentFrame | سطحی برای نمایش محتوای رسانهای که مدیریت نسبت ابعاد، تغییر اندازه و شاتر را مدیریت میکند |
PlayerSurface | سطح خامی که SurfaceView و TextureView را در AndroidView پوشش میدهد. |
دارندگان وضعیت UI
اگر هیچ یک از اجزای scaffolding نیازهای شما را برآورده نمیکند، میتوانید مستقیماً از اشیاء state نیز استفاده کنید. به طور کلی توصیه میشود از متدهای remember مربوطه برای حفظ ظاهر رابط کاربری خود بین recompositionها استفاده کنید.
برای درک بهتر نحوه استفاده از انعطافپذیری نگهدارندههای وضعیت رابط کاربری در مقابل Composableها، در مورد نحوه مدیریت وضعیت توسط Compose مطالعه کنید.
دارندگان حالت دکمه
برای برخی از حالتهای رابط کاربری، کتابخانه فرض را بر این میگذارد که به احتمال زیاد توسط Composableهای دکمهمانند مصرف میشوند.
| ایالت | ایالت را به خاطر بسپار | نوع |
|---|---|---|
PlayPauseButtonState | rememberPlayPauseButtonState | ۲-تغییر وضعیت |
PreviousButtonState | rememberPreviousButtonState | ثابت |
NextButtonState | rememberNextButtonState | ثابت |
RepeatButtonState | rememberRepeatButtonState | ۳-تغییر وضعیت |
ShuffleButtonState | rememberShuffleButtonState | ۲-تغییر وضعیت |
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 یا زمان پوشش آن توسط یک عنصر رابط کاربری نگهدارنده، نگهداری میکند. 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 به شما امکان میدهد شاتر را به عنوان یک لامبدا دنبالهدار سفارشی کنید، اما به طور پیشفرض یک @Composable Box خواهد بود که اندازه کانتینر والد را پر میکند.
فلوز کجا هستند؟
بسیاری از توسعهدهندگان اندروید با استفاده از اشیاء 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 نکند.