توفّر مكتبة 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 لتجاوز زر التشغيل/الإيقاف المؤقت المركزي فقط مع ترك بقية عناصر التحكّم المركزية كما هي.
يمكنك أيضًا تمرير عنصر مركّب مخصّص بالكامل لمعلَمة مثل 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 ) }
عناصر الاحتفاظ بحالة واجهة المستخدم
إذا لم تستوفِ أي من مكوّنات التجميع احتياجاتك، يمكنك أيضًا استخدام عناصر الحالة مباشرةً. يُنصح عمومًا باستخدام طرق remember المقابلة للحفاظ على مظهر واجهة المستخدم بين عمليات إعادة الإنشاء.
عناصر الاحتفاظ بحالة الأزرار
بالنسبة إلى بعض حالات واجهة المستخدم، تفترض المكتبة أنّها ستُستخدم على الأرجح من خلال عناصر مركّبة تشبه الأزرار.
| الحالة | `remember*State` | النوع |
|---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
تبديل ثنائي |
PreviousButtonState |
rememberPreviousButtonState |
ثابت |
NextButtonState |
rememberNextButtonState |
ثابت |
RepeatButtonState |
rememberRepeatButtonState |
تبديل ثلاثي |
ShuffleButtonState |
rememberShuffleButtonState |
تبديل ثنائي |
PlaybackSpeedState |
rememberPlaybackSpeedState |
قائمة أو تبديل متعدد |
مثال على استخدام 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 holds to information for when the video output in a
PlayerSurface can be shown or should be covered by a placeholder UI element.
ContentFrame يجمع العنصر المركّب بين التعامل مع نسبة العرض إلى الارتفاع والتعامل مع عرض الستارة على سطح غير جاهز بعد.
@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 لتغيير حجم السطح
إلى نسبة العرض إلى الارتفاع المختارة (راجِع تغيير حجم المحتوى لمزيد من الأنواع) و
presentationState.coverSurface لمعرفة الوقت غير المناسب لـ
عرض السطح. في هذه الحالة، يمكنك وضع ستارة غير شفافة أعلى السطح، والتي ستختفي عندما يصبح السطح جاهزًا.
ContentFrame يتيح لك تخصيص الستارة كدالة لامدا لاحقة، ولكنّها ستكون تلقائيًا @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. - يمكن إنشاؤها في أماكن متعدّدة في شجرة واجهة المستخدم وستحافظ دائمًا على عرض متّسق لحالة المشغّل.
- تعرض حقول
Stateفي Compose التي يمكن أن يستهلكها عنصر مركّب للاستجابة ديناميكيًا للتغييرات. - تتضمّن دالة
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.