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