کتابخانه 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 پوشش میدهد. |
سفارشیسازی Player قابل ترکیب
هنگام استفاده از کتابخانه media3-ui-compose-material3 ، کامپوننت Player یک تجربه پخش رسانهای کامل را ارائه میدهد. میتوانید با ارائه کامپوننتهای خودتان به اسلاتهای محتوای آن، طرحبندی آن را سفارشی کنید، یا میتوانید از شیء PlayerDefaults برای سفارشیسازی بخشهای خاصی از رابط کاربری، مانند TopControls ، CenterControls ، BottomControls و ErrorOverlay استفاده کنید.
برای مثال، میتوانید از PlayerDefaults.CenterControls برای لغو فقط دکمه پخش/مکث مرکزی استفاده کنید، در حالی که بقیه کنترلهای مرکزی دستنخورده باقی میمانند. همچنین میتوانید یک composable کاملاً سفارشی برای پارامتری مانند topControls ارسال کنید و سایر پارامترها مانند bottomControls را کاملاً حذف کنید تا آنها را به صورت پیشفرض خود نگه دارید.
@Composable fun CustomPlayerSlots(player: Player, modifier: Modifier = Modifier) { Player( player = player, modifier = modifier, topControls = { p, visible -> // Fully custom top controls AnimatedVisibility(visible) { Text("My custom title") } }, centerControls = { p, visible -> // Use default CenterControls but override the central button PlayerDefaults.CenterControls( player = p, visible = visible, central = { // A custom play/pause button Material3PlayPauseButton(it, modifier = Modifier.size(64.dp)) }, ) }, // bottomControls are left as default ) }
دارندگان وضعیت UI
اگر هیچ یک از اجزای scaffolding نیازهای شما را برآورده نمیکند، میتوانید مستقیماً از اشیاء state نیز استفاده کنید. به طور کلی توصیه میشود از متدهای remember مربوطه برای حفظ ظاهر رابط کاربری خود بین recompositionها استفاده کنید.
برای درک بهتر نحوه استفاده از انعطافپذیری نگهدارندههای وضعیت رابط کاربری در مقابل composableها، در مورد نحوه مدیریت وضعیت توسط Compose مطالعه کنید.
دارندگان حالت دکمه
برای برخی از حالتهای رابط کاربری، کتابخانه فرض را بر این میگذارد که به احتمال زیاد توسط کامپوننتهای دکمهمانند مصرف میشوند.
| ایالت | ایالت را به خاطر بسپار | نوع |
|---|---|---|
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), ) }
دارندگان وضعیت خروجی بصری
CurrentMediaItemState اطلاعاتی در مورد MediaItem در حال پخش ارائه میدهد، در حالی که PlaylistState اطلاعاتی در مورد MediaItems تنظیم شده روی پخش کننده را نمایش میدهد. اینها برای نمایش اطلاعات ابرداده در رابط کاربری سفارشی شما مفید هستند.
ErrorState اطلاعاتی در مورد وضعیت خطای فعلی پخشکننده ارائه میدهد که میتواند برای نمایش یک پوشش خطا استفاده شود.
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.videoAspectRatio) // Always leave PlayerSurface to be part of the Compose tree because it will be initialized 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.videoAspectRatio برای مقیاسبندی سطح (Surface) با نسبت ابعاد انتخاب شده (برای انواع بیشتر به Content scale مراجعه کنید) و 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(COMMAND_ACTION_A)) private set var someFieldValue by mutableStateOf(someFieldDefault) private set fun onClick() { player.actionA() } suspend fun observe(): Nothing = player.listen { events -> if (events.containsAny(EVENT_B_CHANGED, EVENT_C_CHANGED, EVENT_AVAILABLE_COMMANDS_CHANGED)) { someFieldValue = this.someField isEnabled = this.isCommandAvailable(COMMAND_ACTION_A) } } }
برای واکنش به Player.Events خودتان، میتوانید آنها را با استفاده از Player.listen دریافت کنید که یک suspend fun است که به شما امکان میدهد وارد دنیای کوروتین شوید و به طور نامحدود به Player.Events گوش دهید. پیادهسازی Media3 از حالتهای مختلف رابط کاربری به توسعهدهنده نهایی کمک میکند تا خود را درگیر یادگیری در مورد Player.Events نکند.