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.7.1")

Groovy

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

Uygulamanızı Compose'u öncelikli tutarak geliştirmenizi veya View'ları kullanmaktan geçiş yapmanızı önemle tavsiye ederiz.

Tamamen Compose ile geliştirilmiş demo uygulaması

media3-ui-compose kitaplığı, kullanıma hazır Composables (ör. düğmeler, göstergeler, resimler veya iletişim kutuları) içermese de PlayerView öğesini AndroidView içine sarmalama gibi birlikte çalışabilirlik çözümlerinden kaçınan, tamamen Compose ile yazılmış bir demo uygulaması bulabilirsiniz. Demo uygulaması, media3-ui-compose modülündeki kullanıcı arayüzü durumu tutucu sınıflarını kullanır ve Compose Material3 kitaplığından yararlanır.

Kullanıcı arayüzü durum koruyucuları

UI durumu tutucularının esnekliğini composable'lara 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 tutucuları

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

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:

@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 öğesini kullanıcı arayüzü durumuna dönüştürmektir.

Ardından, tercih ettiğiniz düzende düğmeleri karıştırıp eşleştirebilirsiniz:

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

Görsel çıkış durum koruyucuları

PresentationState, PlayerSurface içinde 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.

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, hem presentationState.videoSizeDp ile yüzeyi istenen 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 doğru olmadığı zamanı öğrenebiliriz. Bu durumda, yüzeyin üzerine opak bir deklanşör yerleştirebilirsiniz. Bu deklanşör, yüzey hazır olduğunda kaybolur.

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 filter, istediğiniz şekilde kullanabileceğiniz bir öğe olur.

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ı sağlamak ve Player ile senkronize olmayan kullanıcı arayüzü öğelerini göstermemek istiyoruz.

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. Deseni kopyalamak için mevcut durumun kaynak kodunu inceleyin. 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 bilgi için Player.listen bölümüne 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 edin.
  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, suspend fun olan ve eş yordam dünyasına girmenize ve Player.Events'yi süresiz olarak dinlemenize olanak tanıyan bir kitaplıktır. Çeşitli kullanıcı arayüzü durumlarının Media3'te uygulanması, son geliştiricinin Player.Events hakkında bilgi edinmesiyle ilgili endişelenmesine gerek kalmamasını sağlar.