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 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 إلى حالات غير قانونية محتملة في واجهة المستخدم.

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

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

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