وابستگی را اضافه کنید
کتابخانه Media3 شامل یک ماژول رابط کاربری مبتنی بر Jetpack Compose است. برای استفاده از آن، وابستگی زیر را اضافه کنید:
کاتلین
implementation("androidx.media3:media3-ui-compose:1.6.0")
شیار
implementation "androidx.media3:media3-ui-compose:1.6.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.resize(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
نکند.
وابستگی را اضافه کنید
کتابخانه Media3 شامل یک ماژول رابط کاربری مبتنی بر Jetpack Compose است. برای استفاده از آن، وابستگی زیر را اضافه کنید:
کاتلین
implementation("androidx.media3:media3-ui-compose:1.6.0")
شیار
implementation "androidx.media3:media3-ui-compose:1.6.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.resize(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
نکند.