Core Compose

media3-ui-compose kitaplığı, Jetpack Compose'da medya kullanıcı arayüzü oluşturmak için temel bileşenleri sağlar. media3-ui-compose-material3 kitaplığının sunduğundan daha fazla özelleştirme yapması gereken geliştiriciler için tasarlanmıştır. Bu sayfada, özel bir medya oynatıcı kullanıcı arayüzü oluşturmak için temel bileşenlerin ve durum tutucuların nasıl kullanılacağı açıklanmaktadır.

Material3 ve özel Compose bileşenlerini karıştırma

media3-ui-compose-material3 kitaplığı esnek olacak şekilde tasarlanmıştır. Kullanıcı arayüzünüzün çoğu için önceden oluşturulmuş bileşenleri kullanabilirsiniz ancak daha fazla kontrole ihtiyacınız olduğunda tek bir bileşeni özel bir uygulamayla değiştirebilirsiniz. İşte bu noktada media3-ui-compose kitaplığı devreye girer.

Örneğin, Material3 kitaplığındaki standart PreviousButton ve NextButton öğelerini kullanmak istediğinizi ancak tamamen özel bir PlayPauseButton öğesine ihtiyacınız olduğunu varsayalım. Bunu, temel media3-ui-compose kitaplığındaki PlayPauseButton öğesini kullanarak ve önceden oluşturulmuş bileşenlerin yanına yerleştirerek yapabilirsiniz.

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)
}

Kullanılabilir bileşenler

media3-ui-compose kitaplığı, yaygın oynatıcı kontrolleri için önceden oluşturulmuş bir dizi composable sağlar. Uygulamanızda doğrudan kullanabileceğiniz bileşenlerden bazıları şunlardır:

Bileşen Açıklama
PlayPauseButton Oynatma ve duraklatma arasında geçiş yapan bir düğme için durum kapsayıcısı.
SeekBackButton Tanımlanmış bir artışla geriye doğru arama yapan bir düğme için durum kapsayıcısı.
SeekForwardButton Belirli bir artışla ileri sarmayı sağlayan bir düğme için durum kapsayıcısı.
NextButton Sonraki medya öğesine gitmeyi sağlayan bir düğme için durum kapsayıcısı.
PreviousButton Önceki medya öğesine gitmek için kullanılan düğmenin durum kapsayıcısı.
RepeatButton Tekrarlama modları arasında geçiş yapan bir düğme için durum kapsayıcısı.
ShuffleButton Karıştırma modunu açıp kapatan bir düğme için durum kapsayıcısı.
MuteButton Oynatıcıyı sessize alan ve sessizliğini kaldıran bir düğme için durum kapsayıcısı.
TimeText Oyuncunun ilerleme durumunu gösteren composable için durum kapsayıcısı.
ContentFrame En boy oranı yönetimi, yeniden boyutlandırma ve deklanşör işlevlerini yerine getiren, medya içeriklerini görüntülemeye yarayan bir yüzey
PlayerSurface AndroidView içinde SurfaceView ve TextureView öğelerini saran ham yüzey.

Kullanıcı arayüzü durumu depolayıcıları

İskele bileşenlerinden hiçbiri ihtiyaçlarınızı karşılamıyorsa durum nesnelerini doğrudan da kullanabilirsiniz. Genel olarak, yeniden oluşturmalar arasında kullanıcı arayüzünüzün görünümünü korumak için ilgili remember yöntemlerini kullanmanız önerilir.

UI state holder'ların esnekliğini Composables'a kıyasla nasıl kullanabileceğinizi daha iyi anlamak için Compose'un durumu nasıl yönettiği hakkında bilgi edinin.

Düğme durumu depolayıcıları

Kitaplık, bazı kullanıcı arayüzü durumlarında bunların büyük olasılıkla düğme benzeri composable'lar tarafından kullanılacağını varsayar.

Eyalet remember*State Tür
PlayPauseButtonState rememberPlayPauseButtonState 2-Toggle
PreviousButtonState rememberPreviousButtonState Sabit
NextButtonState rememberNextButtonState Sabit
RepeatButtonState rememberRepeatButtonState 3-Toggle
ShuffleButtonState rememberShuffleButtonState 2-Toggle
PlaybackSpeedState rememberPlaybackSpeedState Menü veya N-Toggle

PlayPauseButtonState için örnek kullanım:

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),
  )
}

Görsel çıkış durumu bilgisi depolayıcıları

PresentationState, PlayerSurface içindeki video çıkışının ne zaman gösterilebileceği veya yer tutucu bir kullanıcı arayüzü öğesiyle ne zaman kapatılması gerektiğiyle ilgili bilgileri tutar. ContentFrame Composable, en-boy oranı işlemeyi henüz hazır olmayan bir yüzeyde deklanşörü göstermeyi ele alarak birleştirir.

@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()
  }
}

Burada, hem presentationState.videoSizeDp ile yüzeyi seçilen en-boy oranına göre ölçeklendirebiliriz (daha fazla tür için ContentScale belgelerine bakın) hem de presentationState.coverSurface ile yüzeyin gösterilmesi için zamanlamanın uygun olmadığı anları öğrenebiliriz. Bu durumda, yüzeyin üzerine opak bir örtü yerleştirebilirsiniz. Bu örtü, yüzey hazır olduğunda kaybolur. ContentFrame deklanşörü sondaki bir lambda olarak özelleştirmenize olanak tanır ancak varsayılan olarak üst kapsayıcının boyutunu dolduran siyah bir @Composable Box olur.

Flows nerede bulunur?

Birçok Android geliştirici, sürekli değişen kullanıcı arayüzü verilerini toplamak için Kotlin Flow nesnelerini kullanmaya alışkındır. Örneğin, yaşam döngüsüne duyarlı bir şekilde collect yapabileceğiniz Player.isPlaying akışını arıyor olabilirsiniz. Veya Player.eventsFlow gibi bir şey yazarak Flow<Player.Events> elde edebilirsiniz. Bu Flow<Player.Events>, istediğiniz şekilde filter yapmanıza olanak tanır.

Ancak Player kullanıcı arayüzü durumu için akış kullanmanın bazı dezavantajları vardır. Temel endişelerden biri, veri aktarımının eşzamansız olmasıdır. Player.Event ile kullanıcı arayüzü tarafındaki tüketimi arasında mümkün olduğunca az gecikme olmasını istiyoruz. Bu nedenle, Player ile senkronize olmayan kullanıcı arayüzü öğelerini göstermiyoruz.

Diğer noktalar:

  • Tüm Player.Events'lerin bulunduğu bir akış, tek bir sorumluluk ilkesine uymaz. Her tüketicinin ilgili etkinlikleri filtrelemesi gerekir.
  • Her Player.Event için akış oluşturmak, bunları her kullanıcı arayüzü öğesi için birleştirmenizi (combine ile) gerektirir. Player.Event ile kullanıcı arayüzü öğesi değişikliği arasında çoktan çoğa eşleme vardır. combine kullanmak zorunda kalmak, kullanıcı arayüzünün yasa dışı olabilecek durumlara girmesine neden olabilir.

Özel kullanıcı arayüzü durumları oluşturma

Mevcut kullanıcı arayüzü durumları ihtiyaçlarınızı karşılamıyorsa özel kullanıcı arayüzü durumları ekleyebilirsiniz. Mevcut durumun kaynak kodunu inceleyerek kalıbı kopyalayın. Tipik bir kullanıcı arayüzü durumu tutucu sınıfı şunları yapar:

  1. Player alır.
  2. Coroutine'ları kullanarak Player öğesine abone olur. Daha fazla ayrıntı için Player.listen sayfasına bakın.
  3. İç durumunu güncelleyerek belirli Player.Events yanıt verir.
  4. Uygun bir Player güncellemesine dönüştürülecek iş mantığı komutlarını kabul eder.
  5. Kullanıcı arayüzü ağacının birden fazla yerinde oluşturulabilir ve her zaman oyuncunun durumunun tutarlı bir görünümünü korur.
  6. Değişikliklere dinamik olarak yanıt vermek için bir Composable tarafından kullanılabilen Compose State alanlarını kullanıma sunar.
  7. Besteler arasında örneği hatırlamak için remember*State işleviyle birlikte gelir.

Perde arkasında neler olur?

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)
      }
    }
}

Kendi Player.Events'nize tepki vermek için Player.listen'ı kullanarak onları yakalayabilirsiniz. Player.listen, sizi coroutine dünyasına sokan ve Player.Events'ı süresiz olarak dinlemenize olanak tanıyan bir suspend fun'dir. Çeşitli kullanıcı arayüzü durumlarının Media3'te uygulanması, son geliştiricinin Player.Events hakkında bilgi edinmesiyle ilgilenmemesine yardımcı olur.