Compose tabanlı kullanıcı arayüzünü kullanmaya başlama

Bağımlılığı ekleme

Media3 kitaplığı, Jetpack Compose tabanlı bir kullanıcı arayüzü modülü içerir. Bu özelliği kullanmak için aşağıdaki bağımlılığı ekleyin:

Kotlin

implementation("androidx.media3:media3-ui-compose:1.6.0")

Groovy

implementation "androidx.media3:media3-ui-compose:1.6.0"

Uygulamanızı öncelikli olarak Compose ile geliştirmenizi veya Görünümler'den geçiş yapmanızı önemle tavsiye ederiz.

Tamamen Oluştur demo uygulaması

media3-ui-compose kitaplığı, hazır bileşenler (ör. düğmeler, göstergeler, resimler veya iletişim kutuları) içermese de PlayerView'i AndroidView içine sarmalama gibi herhangi bir birlikte çalışabilirlik çözümünden kaçınan, tamamen Compose'da yazılmış bir demo uygulama bulabilirsiniz. Demo uygulaması, media3-ui-compose modülündeki kullanıcı arayüzü durum tutucu sınıflarını ve Compose Material3 kitaplığını kullanır.

Kullanıcı arayüzü durum koruyucuları

Kullanılabilirlik durumu tutucularının esnekliğini, bileşenlere 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 durum koruyucuları

Bazı kullanıcı arayüzü durumlarının büyük olasılıkla düğme benzeri Composables tarafından kullanılacağını varsayıyoruz.

Eyalet remember*Eyalet 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 kullanımıyla ilgili örnek:

@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 öğesinin, oynatma veya duraklatma için kullanılacak simge gibi tema bilgisi içermediğini unutmayın. Tek sorumluluğu Player değerini kullanıcı arayüzü durumuna dönüştürmektir.

Ardından, düğmeleri tercihinize göre düzenleyebilirsiniz:

Row(
  modifier = modifier.fillMaxWidth(),
  horizontalArrangement = Arrangement.SpaceEvenly,
  verticalAlignment = Alignment.CenterVertically,
) {
  PreviousButton(player)
  PlayPauseButton(player)
  NextButton(player)
}

Görsel çıkış durum koruyucuları

PresentationState, PlayerSurface içindeki video çıkışının ne zaman gösterilebileceği veya yer tutucu kullanıcı arayüzü öğesiyle kapatılması gerektiğiyle ilgili bilgileri tutar.

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

Burada, yüzeyi istenen en boy oranına ölçeklendirmek için presentationState.videoSizeDp'ü (daha fazla tür için ContentScale dokümanlarına bakın) ve yüzeyi göstermenin zamanlamasının ne zaman doğru olmadığını öğrenmek için presentationState.coverSurface'ı kullanabiliriz. Bu durumda, yüzeyin üzerine opak bir perde yerleştirebilirsiniz. Bu perde, yüzey hazır olduğunda kaybolur.

Akışlar nerede bulunur?

Birçok Android geliştiricisi, sürekli değişen kullanıcı arayüzü verilerini toplamak için Kotlin Flow nesnelerini kullanma konusunda bilgi sahibidir. Örneğin, yaşam döngüsü bilinciyle collect yapabileceğiniz bir Player.isPlaying akışı arayabilirsiniz. Dilerseniz Player.eventsFlow gibi bir ifade kullanarak istediğiniz şekilde filter edebileceğiniz bir Flow<Player.Events> elde edebilirsiniz.

Ancak Player kullanıcı arayüzü durumu için akışların kullanılmasının bazı dezavantajları vardır. En önemli endişelerden biri, veri aktarımının eşzamansız olmasıdır. Player.Event ile kullanıcı arayüzünde tüketimi arasında mümkün olduğunca az gecikme olmasını sağlamak ve Player ile senkronize olmayan kullanıcı arayüzü öğelerini göstermekten kaçınmak istiyoruz.

Diğer noktalar şunlardır:

  • Tüm Player.Events'leri içeren bir akış tek bir sorumluluk ilkesine uymaz. Bu durumda her tüketicinin alakalı etkinlikleri filtrelemesi gerekir.
  • Her Player.Event için bir akış oluşturmak istiyorsanız her kullanıcı arayüzü öğesi için bunları (combine ile) birleştirmeniz gerekir. Player.Event ile kullanıcı arayüzü öğesi değişikliği arasında çoklu eşleme vardır. combine kullanmak zorunda kalmak, kullanıcı arayüzünün yasa dışı durumlara girmesine neden olabilir.

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

Mevcut kullanıcı arayüzü durumları ihtiyaçlarınıza uygun değilse özel kullanıcı arayüzü durumları ekleyebilirsiniz. Kalıpları kopyalamak için mevcut durumun kaynak kodunu inceleyin. Tipik bir kullanıcı arayüzü durum tutucu sınıfı aşağıdakileri yapar:

  1. Player alır.
  2. Player dizisine coroutine'leri kullanarak abone olur. Daha fazla bilgi için Player.listen bölümüne bakın.
  3. Dahili durumunu güncelleyerek belirli Player.Events öğelerine yanıt verir.
  4. Uygun bir Player güncellemesine dönüştürülecek iş mantığı komutlarını kabul edin.
  5. Kullanıcı arayüzü ağacında birden fazla yerde oluşturulabilir ve her zaman oyuncunun durumunu tutarlı bir şekilde gösterir.
  6. Değişikliklere dinamik olarak yanıt vermek için bir Composable tarafından kullanılabilen Compose State alanlarını gösterir.
  7. Kompozisyonlar arasındaki örneği hatırlamak için remember*State işlevi bulunur.

Kamera arkası:

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'i kullanabilirsiniz. Bu, coroutine dünyasına girmenizi ve Player.Events'yi süresiz olarak dinlemenizi sağlayan bir suspend fun'dir. Çeşitli kullanıcı arayüzü durumlarının Media3 tarafından uygulanması, son geliştiricinin Player.Events hakkında bilgi edinme konusunda endişelenmemesine yardımcı olur.