إضافة التبعية
تتضمّن مكتبة Media3 وحدة واجهة مستخدِم مستندة إلى Jetpack Compose. لاستخدامها، أضِف التبعية التالية:
Kotlin
implementation("androidx.media3:media3-ui-compose:1.6.0")
رائع
implementation "androidx.media3:media3-ui-compose:1.6.0"
ننصحك بشدة بتطوير تطبيقك باستخدام ميزة "الإنشاء" أولاً أو التوقف عن استخدام "العروض".
تطبيق تجريبي لميزة "الإنشاء بالكامل"
على الرغم من أنّ مكتبة media3-ui-compose
لا تتضمّن عناصر قابلة للدمج (مثل الأزرار أو المؤشرات أو الصور أو مربّعات الحوار) جاهزة للاستخدام، يمكنك العثور على
تطبيق تجريبي مكتوب بالكامل بلغة Compose يتجنب أي حلول تتعلّق بالتشغيل التفاعلي، مثل لف PlayerView
في AndroidView
. يستخدِم التطبيق التجريبي
فئات حامل حالة واجهة المستخدم من وحدة media3-ui-compose
ويستفيد
من مكتبة Compose Material3.
عناصر الاحتفاظ بحالة واجهة المستخدم
لفهم كيفية الاستفادة بشكل أفضل من مرونة حوامل حالة واجهة المستخدم مقارنةً بالعناصر القابلة للتجميع، يمكنك الاطّلاع على كيفية إدارة Compose لحالة التطبيق.
عناصر الاحتفاظ بحالة الأزرار
بالنسبة إلى بعض حالات واجهة المستخدم، نفترض أنّه من المرجّح أن يتم استخدامها من خلال عناصر تركيبية تشبه الأزرار.
الولاية | remember*الحالة | النوع |
---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2-تبديل |
PreviousButtonState |
rememberPreviousButtonState |
ثابت |
NextButtonState |
rememberNextButtonState |
ثابت |
RepeatButtonState |
rememberRepeatButtonState |
3-Toggle |
ShuffleButtonState |
rememberShuffleButtonState |
2-تبديل |
PlaybackSpeedState |
rememberPlaybackSpeedState |
القائمة أو التبديل المتعدد |
مثال على استخدام 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
إلى حالة واجهة المستخدم.
يمكنك بعد ذلك دمج الأزرار ومطابقتها في التنسيق المفضّل لديك:
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
عناصر الاحتفاظ بحالة الإخراج المرئي
يحتوي PresentationState
على معلومات حول الحالات التي يمكن فيها عرض ناتج الفيديو في ملف
PlayerSurface
أو الحالات التي يجب فيها تغطية ناتج الفيديو بعنصر واجهة مستخدم نائب.
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
لمعرفة الحالات التي لا يكون فيها التوقيت مناسبًا ل
عرض المساحة. في هذه الحالة، يمكنك وضع غالق معتم فوق سطح التصوير، والذي سيختفي عندما يصبح السطح جاهزًا.
أين يمكنني العثور على "عمليات التنقّل"؟
يُعرف العديد من مطوّري تطبيقات Android باستخدام عناصر Flow
في Kotlin لجمع
بيانات واجهة المستخدم المتغيّرة باستمرار. على سبيل المثال، قد تبحث عن تدفق
Player.isPlaying
يمكنك collect
بطريقة تراعي دورة الحياة. أو
شيء مثل Player.eventsFlow
لتزويدك بـ Flow<Player.Events>
يمكنك filter
بالطريقة التي تريدها.
ومع ذلك، فإنّ استخدام مسارات الحالة Player
لواجهة المستخدم له بعض السلبيات. ومن بين الملاحظات العميقة التي يجب أخذها في الاعتبار، الطبيعة غير المتزامنة لنقل البيانات. نريد التأكّد من خفض وقت الاستجابة إلى الحد الأدنى بين Player.Event
واستهلاكه من جهة ملف Player
، مع تجنُّب عرض عناصر واجهة المستخدم غير المتزامنة مع Player
.
تشمل النقاط الأخرى ما يلي:
- إنّ مسارًا يتضمّن جميع
Player.Events
لن يلتزم بمبدأ واحد للمسؤولية، وسيتعين على كل مستهلك فلترة الأحداث ذات الصلة. - سيتطلب إنشاء مسار لكل
Player.Event
دمجها (باستخدامcombine
) لكل عنصر من عناصر واجهة المستخدم. هناك تعيين بين عناصر متعدّدة بين Player.Event وتغيير عنصر واجهة المستخدم. قد يؤدي استخدامcombine
إلى نقل واجهة المستخدم إلى حالات يُحتمل أن تكون غير قانونية.
إنشاء حالات واجهة مستخدم مخصّصة
يمكنك إضافة حالات واجهة مستخدم مخصّصة إذا لم تستوفِ الحالات الحالية احتياجاتك. اطّلِع على رمز المصدر للحالة الحالية لنسخ النمط. تُجري فئة حامل حالة واجهة المستخدم النموذجية ما يلي:
- يأخذ
Player
. - يؤدي هذا الإجراء إلى الاشتراك في
Player
باستخدام وظائف التشغيل المتعدّد. يُرجى الاطّلاع علىPlayer.listen
للحصول على مزيد من التفاصيل. - الاستجابة لـ
Player.Events
معيّن من خلال تعديل حالته الداخلية - قبول أوامر منطق النشاط التجاري التي سيتم تحويلها إلى تعديل مناسب
Player
- يمكن إنشاؤها في مواضع متعددة في شجرة واجهة المستخدم، وستحافظ دائمًا على عرض متناسق لحالة المشغّل.
- تعرِض حقول Compose
State
التي يمكن استخدامها من خلال عنصر Composable ل reagting بشكل ديناميكي إلى التغييرات. - تأتي مع دالة
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 لحالات واجهة المستخدم المختلفة المطوّر النهائي على عدم القلق بشأن التعرّف على
Player.Events
.