کتابخانه 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 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() } }
Here, we can use both presentationState.videoSizeDp to scale the Surface to the chosen aspect ratio (see ContentScale docs for more types) and presentationState.coverSurface to know when the timing is not right to be showing the Surface. In this case, you can position an opaque shutter on top of the surface, which will disappear when the surface becomes ready. ContentFrame lets you customize the shutter as a trailing lambda, but by default it will be a black @Composable Box filling the size of the parent container.
فلوز کجا هستند؟
بسیاری از توسعهدهندگان اندروید با استفاده از اشیاء Kotlin Flow برای جمعآوری دادههای رابط کاربری که دائماً در حال تغییر هستند، آشنا هستند. برای مثال، ممکن است به دنبال جریان Player.isPlaying باشید که بتوانید آن را به شیوهای آگاه از چرخه حیات collect . یا چیزی مانند Player.eventsFlow که Flow<Player.Events> را در اختیار شما قرار میدهد که میتوانید آن را به دلخواه filter .
با این حال، استفاده از جریانها برای وضعیت رابط کاربری Player دارای معایبی است. یکی از نگرانیهای اصلی، ماهیت ناهمزمان انتقال دادهها است. ما میخواهیم تا حد امکان تأخیر کمی بین یک Player.Event و مصرف آن در سمت رابط کاربری داشته باشیم و از نمایش عناصر رابط کاربری که با Player ناهمگام هستند، اجتناب کنیم.
نکات دیگر عبارتند از:
- یک جریان با تمام
Player.Eventsبه یک اصل مسئولیت واحد پایبند نیست، هر مصرفکننده باید رویدادهای مربوطه را فیلتر کند. - Creating a flow for each
Player.Eventwill require you to combine them (withcombine) for each UI element. There is a many-to-many mapping between a Player.Event and a UI element change. Having to usecombinecould lead the UI to potentially illegal states.
ایجاد حالتهای رابط کاربری سفارشی
اگر حالتهای رابط کاربری موجود نیازهای شما را برآورده نمیکنند، میتوانید حالتهای رابط کاربری سفارشی اضافه کنید. برای کپی کردن الگو، کد منبع حالت موجود را بررسی کنید. یک کلاس نگهدارنده حالت رابط کاربری معمولی موارد زیر را انجام میدهد:
- یک
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) } } }
To react to your own Player.Events , you can catch them using Player.listen which is a suspend fun that lets you enter the coroutine world and indefinitely listen to Player.Events . Media3 implementation of various UI states helps the end developer not to concern themselves with learning about Player.Events .
کتابخانه 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 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() } }
Here, we can use both presentationState.videoSizeDp to scale the Surface to the chosen aspect ratio (see ContentScale docs for more types) and presentationState.coverSurface to know when the timing is not right to be showing the Surface. In this case, you can position an opaque shutter on top of the surface, which will disappear when the surface becomes ready. ContentFrame lets you customize the shutter as a trailing lambda, but by default it will be a black @Composable Box filling the size of the parent container.
فلوز کجا هستند؟
بسیاری از توسعهدهندگان اندروید با استفاده از اشیاء 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 نکند.