Biblioteka media3-ui-compose zawiera podstawowe komponenty do tworzenia interfejsu multimedialnego w Jetpack Compose. Jest przeznaczony dla deweloperów, którzy potrzebują większych możliwości dostosowywania niż te, które oferuje media3-ui-compose-material3biblioteka. Na tej stronie dowiesz się, jak za pomocą podstawowych komponentów i obiektów przechowujących stan utworzyć niestandardowy interfejs odtwarzacza multimediów.
Łączenie komponentów Material3 i komponentów niestandardowych Compose
Biblioteka media3-ui-compose-material3 została zaprojektowana tak, aby była elastyczna. Większość interfejsu możesz utworzyć za pomocą gotowych komponentów, ale w razie potrzeby możesz zastąpić pojedynczy komponent implementacją niestandardową, aby uzyskać większą kontrolę. W tym momencie przydaje się biblioteka media3-ui-compose.
Załóżmy, że chcesz użyć standardowych elementów PreviousButton i NextButton z biblioteki Material3, ale potrzebujesz całkowicie niestandardowego elementu PlayPauseButton. Możesz to zrobić, używając komponentu PlayPauseButton z biblioteki podstawowej media3-ui-compose i umieszczając go obok gotowych komponentów.
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) }
Dostępne komponenty
media3-ui-compose Biblioteka zawiera zestaw gotowych komponentów kompozycyjnych do typowych elementów sterujących odtwarzacza. Oto niektóre komponenty, których możesz używać bezpośrednio w aplikacji:
| Komponent | Opis |
|---|---|
PlayPauseButton |
Kontener stanu przycisku, który przełącza się między odtwarzaniem i wstrzymaniem. |
SeekBackButton |
Kontener stanu przycisku, który przewija do tyłu o określony przyrost. |
SeekForwardButton |
Kontener stanu przycisku, który przewija do przodu o określony przyrost. |
NextButton |
Kontener stanu przycisku, który przechodzi do następnego elementu multimedialnego. |
PreviousButton |
Kontener stanu przycisku, który przewija do poprzedniego elementu multimedialnego. |
RepeatButton |
Kontener stanu przycisku, który przełącza tryby powtarzania. |
ShuffleButton |
Kontener stanu przycisku, który przełącza tryb losowego odtwarzania. |
MuteButton |
Kontener stanu przycisku, który wycisza i wyłącza wyciszenie odtwarzacza. |
TimeText |
Kontener stanu dla komponentu, który wyświetla postęp odtwarzania. |
ContentFrame |
Powierzchnia do wyświetlania treści multimedialnych, która zarządza współczynnikiem proporcji, zmianą rozmiaru i migawką. |
PlayerSurface |
Surowa powierzchnia, która opakowuje elementy SurfaceView i TextureView w AndroidView. |
Zmienne stanów interfejsu
Jeśli żaden z komponentów szkieletowych nie spełnia Twoich potrzeb, możesz też użyć bezpośrednio obiektów stanu. Zwykle zaleca się używanie odpowiednich metod remember, aby zachować wygląd interfejsu podczas ponownego komponowania.
Aby lepiej zrozumieć, jak wykorzystać elastyczność elementów przechowujących stan interfejsu w porównaniu z funkcjami kompozycyjnymi, przeczytaj artykuł o tym, jak Compose zarządza stanem.
Zmienne stanu przycisku
W przypadku niektórych stanów interfejsu biblioteka zakłada, że będą one najprawdopodobniej używane przez komponenty kompozycyjne podobne do przycisków.
| Województwo | remember*State | Typ |
|---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2-Toggle |
PreviousButtonState |
rememberPreviousButtonState |
Stała |
NextButtonState |
rememberNextButtonState |
Stała |
RepeatButtonState |
rememberRepeatButtonState |
3-Toggle |
ShuffleButtonState |
rememberShuffleButtonState |
2-Toggle |
PlaybackSpeedState |
rememberPlaybackSpeedState |
Menu lub N-Toggle |
Przykłady użycia 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), ) }
Zmienne stanu danych wyjściowych wizualnych
PresentationState zawiera informacje o tym, kiedy można wyświetlić dane wyjściowe wideo w
PlayerSurface lub kiedy powinny być one zastąpione elementem interfejsu użytkownika.
ContentFrame Komponent łączy obsługę proporcji obrazu z wyświetlaniem migawki na powierzchni, która nie jest jeszcze gotowa.
@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() } }
Możemy tu użyć zarówno presentationState.videoSizeDp, aby dopasować powierzchnię do wybranego współczynnika proporcji (więcej typów znajdziesz w dokumentacji ContentScale), jak i presentationState.coverSurface, aby wiedzieć, kiedy nie jest odpowiedni czas na wyświetlanie powierzchni. W takim przypadku możesz umieścić nieprzezroczystą zasłonę na powierzchni, która zniknie, gdy będzie gotowa. ContentFrame
umożliwia dostosowanie migawki jako lambdy końcowej, ale domyślnie będzie to czarna @Composable Box wypełniająca rozmiar kontenera nadrzędnego.
Gdzie znajdę Flows?
Wielu deweloperów Androida wie, jak używać obiektów Kotlin Flow do zbierania stale zmieniających się danych interfejsu. Możesz na przykład szukać Player.isPlaying, które można collect w sposób uwzględniający cykl życia. lub coś w rodzaju Player.eventsFlow, aby zapewnić Ci Flow<Player.Events>, które możesz filter w dowolny sposób.
Używanie przepływów do zarządzania stanem interfejsu Player ma jednak pewne wady. Jednym z głównych problemów jest asynchroniczny charakter przesyłania danych. Chcemy osiągnąć jak najmniejsze opóźnienie między Player.Event a jego wykorzystaniem po stronie interfejsu, unikając wyświetlania elementów interfejsu, które nie są zsynchronizowane z Player.
Inne kwestie:
- Przepływ z samymi
Player.Eventsnie byłby zgodny z zasadą pojedynczej odpowiedzialności, ponieważ każdy odbiorca musiałby odfiltrowywać odpowiednie zdarzenia. - Utworzenie przepływu dla każdego elementu
Player.Eventbędzie wymagać połączenia ich (za pomocą elementucombine) w przypadku każdego elementu interfejsu. Istnieje relacja wiele-do-wielu między zdarzeniem Player.Event a zmianą elementu interfejsu. Używaniecombinemoże spowodować, że interfejs użytkownika znajdzie się w stanie potencjalnie niezgodnym z prawem.
Tworzenie niestandardowych stanów interfejsu
Jeśli istniejące stany interfejsu nie spełniają Twoich potrzeb, możesz dodać stany niestandardowe. Sprawdź kod źródłowy istniejącego stanu, aby skopiować wzorzec. Typowa klasa przechowująca stan interfejsu:
- Przyjmuje wartość
Player. - Subskrybuje
Playerza pomocą współprogramów. Więcej informacji znajdziesz w sekcjiPlayer.listen. - Odpowiada na określone
Player.Events, aktualizując swój stan wewnętrzny. - Akceptuje polecenia logiki biznesowej, które zostaną przekształcone w odpowiednią
Playeraktualizację. - Można go utworzyć w wielu miejscach w drzewie interfejsu i zawsze będzie on wyświetlać spójny widok stanu odtwarzacza.
- Udostępnia pola
StateCompose, które mogą być używane przez funkcję kompozycyjną, aby dynamicznie reagować na zmiany. - Zawiera funkcję
remember*State, która zapamiętuje instancję między kompozycjami.
Co dzieje się za kulisami:
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)
}
}
}
Aby reagować na własne Player.Events, możesz je przechwytywać za pomocą Player.listen, czyli suspend fun, które pozwala wejść do świata korutyn i bezterminowo nasłuchiwać Player.Events. Implementacja różnych stanów interfejsu w Media3 pomaga programistom nie przejmować się Player.Events.