Core Compose

توفّر مكتبة 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 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, scaledModifier, surfaceType)

  if (presentationState.coverSurface) {
    // Cover the surface that is being prepared with a shutter
    shutter()
  }
}

يمكننا هنا استخدام كل من presentationState.videoSizeDp لتغيير حجم Surface إلى نسبة العرض إلى الارتفاع المحدّدة (راجِع مستندات ContentScale لمعرفة المزيد من الأنواع) وpresentationState.coverSurface لمعرفة الوقت غير المناسب لعرض Surface. في هذه الحالة، يمكنك وضع غطاء معتم فوق السطح، وسيختفي عندما يصبح السطح جاهزًا. تتيح لك السمة 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 إلى حالات غير قانونية محتملة في واجهة المستخدم.

إنشاء حالات واجهة مستخدم مخصّصة

يمكنك إضافة حالات واجهة مستخدم مخصّصة إذا لم تلبِّ الحالات الحالية احتياجاتك. اطّلِع على رمز المصدر للحالة الحالية لنسخ النمط. تتضمّن فئة حامل حالة واجهة المستخدم النموذجية ما يلي:

  1. تتضمّن Player.
  2. يؤدي هذا الإجراء إلى الاشتراك في Player باستخدام إجراءات فرعية. لمزيد من التفاصيل، يمكنك الاطّلاع على Player.listen.
  3. يستجيب Player.Events معيّن من خلال تعديل حالته الداخلية.
  4. يقبل أوامر منطق النشاط التجاري التي سيتم تحويلها إلى Player تعديل مناسب.
  5. يمكن إنشاء هذا العنصر في مواضع متعددة ضمن شجرة واجهة المستخدِم، وسيحتفظ دائمًا بعرض متسق لحالة اللاعب.
  6. تعرض هذه السمة حقول State في Compose التي يمكن أن تستهلكها عناصر قابلة للإنشاء من أجل الاستجابة ديناميكيًا للتغييرات.
  7. تتضمّن هذه السمة الدالة 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.