توفّر مكتبة 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 |
عناصر الاحتفاظ بحالة واجهة المستخدم
إذا لم يستوفِ أي من مكونات إنشاء البنية احتياجاتك، يمكنك أيضًا استخدام عناصر الحالة مباشرةً. بشكل عام، يُنصح باستخدام طرق remember المقابلة للحفاظ على مظهر واجهة المستخدم بين عمليات إعادة الإنشاء.
للتعرّف بشكل أفضل على كيفية الاستفادة من مرونة أدوات الاحتفاظ بحالة واجهة المستخدم مقارنةً بـ Composables، يمكنك الاطّلاع على كيفية إدارة الحالة في Compose.
عناصر الاحتفاظ بحالة الأزرار
بالنسبة إلى بعض حالات واجهة المستخدم، تفترض المكتبة أنّه من المرجّح أن يتم استهلاكها من خلال عناصر Composables تشبه الأزرار.
| الولاية | remember*State | النوع |
|---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2-Toggle |
PreviousButtonState |
rememberPreviousButtonState |
ثابت |
NextButtonState |
rememberNextButtonState |
ثابت |
RepeatButtonState |
rememberRepeatButtonState |
3-Toggle |
ShuffleButtonState |
rememberShuffleButtonState |
2-Toggle |
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() } }
في هذا المثال، يمكننا استخدام كل من presentationState.videoSizeDp لتغيير حجم Surface إلى نسبة العرض إلى الارتفاع المحدّدة (راجِع مستندات ContentScale لمعرفة المزيد من الأنواع) وpresentationState.coverSurface لمعرفة الوقت غير المناسب لعرض Surface. في هذه الحالة، يمكنك وضع غطاء معتم فوق السطح، وسيختفي عندما يصبح السطح جاهزًا. تتيح لك السمة ContentFrame تخصيص غالق العدسة كدالة lambda لاحقة، ولكن سيكون الغالق تلقائيًا عبارة عن @Composable Box أسود يملأ حجم الحاوية الرئيسية.
أين يمكن العثور على "المهام"؟
يعرف العديد من مطوّري تطبيقات Android كيفية استخدام عناصر 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التي يمكن أن تستهلكها إحدى الوحدات القابلة للإنشاء من أجل الاستجابة بشكل ديناميكي للتغييرات. - تتضمّن هذه السمة الدالة
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.