Karta multimediów to samodzielna grupa ViewGroup, która wyświetla metadane multimediów, takie jak tytuł, okładka albumu itp., oraz elementy sterujące odtwarzaniem, takie jak Odtwórz, Wstrzymaj, Pomiń, a nawet działania niestandardowe udostępniane przez aplikację multimedialną innej firmy. Karta multimediów może też wyświetlać kolejkę elementów multimedialnych, takich jak playlista.
Rysunek 1. Przykładowe implementacje karty multimedialnej
Jak karty multimediów są implementowane w AAOS?
Grupy widoku, które wyświetlają informacje o multimediach, obserwują aktualizacje LiveData z modelu car-media-common
biblioteki data (PlaybackViewModel
), aby wypełnić dane w grupie widoku. Każda aktualizacja LiveData odpowiada podzbiorowi informacji o multimediach, które uległy zmianie, np. MediaItemMetadata
, PlaybackStateWrapper
i MediaSource
.
Takie podejście prowadzi do powtarzania kodu (każda aplikacja klienta dodaje obserwatorów do każdego elementu LiveData, a wiele podobnych widoków ma przypisane zaktualizowane dane), dlatego utworzyliśmy PlaybackCardController
.
PlaybackCardController
Do biblioteki car-media-common
został dodany element PlaybackCardController
, aby ułatwić tworzenie karty medialnej. To publiczna klasa utworzona z ViewGroup (mView
), PlaybackViewModel (mDataModel
), PlaybackCardViewModel (mViewModel
) i instancji MediaItemsRepository
(mItemsRepository
).
W funkcji setupController
grupa ViewGroup jest analizowana pod kątem określonych widoków za pomocą identyfikatora mView.findViewById(R.id.xxx)
i przypisywana do chronionych obiektów View.
private void getViewsFromWidget() {
mTitle = mView.findViewById(R.id.title);
mAlbumCover = mView.findViewById(R.id.album_art);
mDescription = mView.findViewById(R.id.album_title);
mLogo = mView.findViewById(R.id.content_format);
mAppIcon = mView.findViewById(R.id.media_widget_app_icon);
mAppName = mView.findViewById(R.id.media_widget_app_name);
// ...
}
Każda aktualizacja LiveData z PlaybackViewModel
jest obserwowana w chronionej metodzie i wykonywana w ramach interakcji z widokami odpowiednimi do otrzymanych danych. Na przykład obserwator w MediaItemMetadata
ustawia tytuł na mTitle
TextView
i przekazuje MediaItemMetadata.ArtworkRef
do okładki albumu ImageBinder
mAlbumArtBinder
. Jeśli metadane mają wartość null, widoki są ukryte. Podklasy kontrolera mogą w razie potrzeby zastąpić tę logikę.
mDataModel.getMetadata().observe(mViewLifecycle, this::updateMetadata);
// ...
/** Update views with {@link MediaItemMetadata} */
protected void updateMetadata(MediaItemMetadata metadata) {
if (metadata != null) {
String defaultTitle = mView.getContext().getString(
R.string.metadata_default_title);
updateTextViewAndVisibility(mTitle, metadata.getTitle(), defaultTitle);
updateTextViewAndVisibility(mSubtitle, metadata.getSubtitle());
updateMediaLink(mSubtitleLinker,metadata.getSubtitleLinkMediaId());
updateTextViewAndVisibility(mDescription, metadata.getDescription());
updateMediaLink(mDescriptionLinker, metadata.getDescriptionLinkMediaId());
updateMetadataAlbumCoverArtworkRef(metadata.getArtworkKey());
updateMetadataLogoWithUri(metadata);
} else {
ViewUtils.setVisible(mTitle, false);
ViewUtils.setVisible(mSubtitle, false);
ViewUtils.setVisible(mAlbumCover, false);
ViewUtils.setVisible(mDescription, false);
ViewUtils.setVisible(mLogo, false);
}
}
Rozszerzenie klasy PlaybackCardController
Aplikacje klienckie, które chcą utworzyć kartę multimedialną, powinny rozszerzać PlaybackCardController
, jeśli mają dodatkowe możliwości obsługi przy każdej aktualizacji LiveData. Obecni klienci w AAOS postępują zgodnie z tym wzorcem.
Najpierw należy utworzyć podklasę PlaybackCardController
, taką jak MediaCardController
. Następnie klasa MediaCardController
powinna dodać stałą wewnętrzną klasę Builder, która rozszerza klasę PlaybackCardController
.
public class MediaCardController extends PlaybackCardController {
// extra fields specific to MediaCardController
/** Builder for {@link MediaCardController}. Overrides build() method to
* return NowPlayingController rather than base {@link PlaybackCardController}
*/
public static class Builder extends PlaybackCardController.Builder {
@Override
public MediaCardController build() {
MediaCardController controller = new MediaCardController(this);
controller.setupController();
return controller;
}
}
public MediaCardController(Builder builder) {
super(builder);
// any other function calls needed in constructor
// ...
}
}
Tworzenie wystąpienia klasy PlaybackCardController lub jej podklasy
Klasa kontrolera powinna być utworzona z fragmentu lub aktywności, aby mieć wartość LifecycleOwner dla obserwacji LiveData.
mMediaCardController = (MediaCardController) new MediaCardController.Builder()
.setModels(mViewModel.getPlaybackViewModel(),
mViewModel,
mViewModel.getMediaItemsRepository())
.setViewGroup((ViewGroup) view)
.build();
mViewModel
jest wystąpieniem klasy PlaybackCardViewModel
(lub jej podklasy).
PlaybackCardViewModel do zapisywania stanu
PlaybackCardViewModel
to ViewModel zapisujący stan powiązany z Fragmentem lub Aktywnością, który powinien służyć do odtwarzania zawartości karty multimedialnej w przypadku zmiany konfiguracji (np. przejścia z motywu jasnego na ciemny, gdy użytkownik jedzie przez tunel). Domyślna wartość PlaybackCardViewModel
obsługuje przechowywanie instancji MediaModel
na potrzeby odtwarzania, z których można pobrać uprawnienia PlaybackViewModel
i MediaItemsRepository
. Użyj funkcjiPlaybackCardViewModel
, aby śledzić stan kolejki, historii i menu przepełnienia za pomocą udostępnionych metod getter i setter.
public class PlaybackCardViewModel extends AndroidViewModel {
private MediaModels mModels;
private boolean mNeedsInitialization = true;
private boolean mQueueVisible = false;
private boolean mHistoryVisible = false;
private boolean mOverflowExpanded = false;
public PlaybackCardViewModel(@NonNull Application application) {
super(application);
}
/** Initialize the PlaybackCardViewModel */
public void init(MediaModels models) {
mModels = models;
mNeedsInitialization = false;
}
/**
* Returns whether the ViewModel needs to be initialized. The ViewModel may
* need re-initialization if a config change occurs or if the system kills
* the Fragment.
*/
public boolean needsInitialization() {
return mNeedsInitialization;
}
public MediaItemsRepository getMediaItemsRepository() {
return mModels.getMediaItemsRepository();
}
public PlaybackViewModel getPlaybackViewModel() {
return mModels.getPlaybackViewModel();
}
public MediaSourceViewModel getMediaSourceViewModel() {
return mModels.getMediaSourceViewModel();
}
public void setQueueVisible(boolean visible) {
mQueueVisible = visible;
}
public boolean getQueueVisible() {
return mQueueVisible;
}
public void setHistoryVisible(boolean visible) {
mHistoryVisible = visible;
}
public boolean getHistoryVisible() {
return mHistoryVisible;
}
public void setOverflowExpanded(boolean expanded) {
mOverflowExpanded = expanded;
}
public boolean getOverflowExpanded() {
return mOverflowExpanded;
}
}
Ta klasa może zostać rozszerzona, jeśli trzeba śledzić dodatkowe stany.
Pokazywanie kolejki na karcie multimediów
Interfejs PlaybackViewModel
udostępnia interfejsy LiveData API, które umożliwiają wykrycie, czy MediaSource obsługuje kolejkę, oraz pobranie z niej listy obiektów MediaItemMetadata
. Chociaż tych interfejsów API można używać bezpośrednio do wypełniania obiektu RecyclerView
informacjami o kolejce, do biblioteki car-media-common
dodaliśmy klasę PlaybackQueueController
, aby usprawnić ten proces. Układ każdego elementu w CarUiRecyclerView
jest określany przez aplikację klienta, a także przez opcjonalny układ nagłówka. Aplikacja klienta może też ograniczyć liczbę elementów wyświetlanych w kolejce podczas jazdy za pomocą niestandardowych ograniczeń UXR.
W przykładzie poniżej przedstawiono konstruktor i setki ustawiające PlaybackQueueController
. Zasoby układu queueResource
i headerResource
mogą być przekazywane jako Resources.ID_NULL
, jeśli w pierwszym przypadku kontener zawiera już CarUiRecyclerView
z id queue_list
, a w drugim przypadku kolejka nie ma nagłówka.
/**
* Construct a PlaybackQueueController. If clients don't have a separate
* layout for the queue, where the queue is already inflated within the
* container, they should pass {@link Resources.ID_NULL} as the LayoutRes
* resource. If clients don't require a UxrContentLimiter, they should pass
* null for uxrContentLimiter and the int passed for uxrConfigurationId will
* be ignored.
*/
public PlaybackQueueController(
ViewGroup container,
@LayoutRes int queueResource,
@LayoutRes int queueItemResource,
@LayoutRes int headerResource,
LifecycleOwner lifecycleOwner,
PlaybackViewModel playbackViewModel,
MediaItemsRepository itemsRepository,
@Nullable LifeCycleObserverUxrContentLimiter uxrContentLimiter,
int uxrConfigurationId) {
// ...
}
public void setShowTimeForActiveQueueItem(boolean show) {
mShowTimeForActiveQueueItem = show;
}
public void setShowIconForActiveQueueItem(boolean show) {
mShowIconForActiveQueueItem = show;
}
public void setShowThumbnailForQueueItem(boolean show) {
mShowThumbnailForQueueItem = show;
}
public void setShowSubtitleForQueueItem(boolean show) {
mShowSubtitleForQueueItem = show;
}
/** Calls {@link RecyclerView#setVerticalFadingEdgeEnabled(boolean)} */
public void setVerticalFadingEdgeLengthEnabled(boolean enabled) {
mQueue.setVerticalFadingEdgeEnabled(enabled);
}
public void setCallback(PlaybackQueueCallback callback) {
mPlaybackQueueCallback = callback;
}
Układ każdego elementu kolejki powinien zawierać identyfikatory widoków danych, które chce pokazać, które odpowiadają tym używanym w klasie wewnętrznej QueueViewHolder
.
QueueViewHolder(View itemView) {
super(itemView);
mView = itemView;
mThumbnailContainer = itemView.findViewById(R.id.thumbnail_container);
mThumbnail = itemView.findViewById(R.id.thumbnail);
mSpacer = itemView.findViewById(R.id.spacer);
mTitle = itemView.findViewById(R.id.queue_list_item_title);
mSubtitle = itemView.findViewById(R.id.queue_list_item_subtitle);
mCurrentTime = itemView.findViewById(R.id.current_time);
mMaxTime = itemView.findViewById(R.id.max_time);
mTimeSeparator = itemView.findViewById(R.id.separator);
mActiveIcon = itemView.findViewById(R.id.now_playing_icon);
// ...
}
Aby wyświetlić kolejkę na karcie multimediów utworzonej za pomocą klasy PlaybackCardController
(lub podklasy), można utworzyć element PlaybackQueueController
w konstruktorze PlaybackCardController
, używając odpowiednio funkcji mDataModel
i mItemsRepository
w instancjach PlaybackViewModel
i MediaItemsRepository
.
wyświetlać historię wcześniej odtwarzanych źródeł multimediów.
Z tej sekcji dowiesz się, jak wyświetlać i wyświetlać historię wcześniej odtwarzanych źródeł multimediów.
Pobieranie listy historii za pomocą interfejsu PlaybackCardViewModel API
PlaybackCardViewModel
udostępnia interfejs LiveData API o nazwie getHistoryList()
, który pobiera listę historii multimediów. Zwraca on dane LiveData zawierające listę źródeł multimediów, które były odtwarzane wcześniej. Tych danych można użyć do wypełnienia obiektu CarUiRecyclerView
. Aby usprawnić proces, do biblioteki car-media-common
dodano klasę PlaybackHistoryController
, która jest podobna do klasy PlaybackQueueController
.
public class PlaybackCardViewModel extends AndroidViewModel {
public PlaybackCardViewModel(@NonNull Application application) {
}
/** Initialize the PlaybackCardViewModel */
public void init(MediaModels models) {
}
public LiveData<List<MediaSource>> getHistoryList() {
return mHistoryListData;
}
}
Interfejs historii Surface z PlaybackHistoryController
Użyj nowego elementu PlaybackHistoryController
, aby wypełnić dane historyczne w elementach CarUiRecyclerView
. Konstruktory i główne funkcje tej klasy są następujące: Kontener przekazany przez aplikację klienta powinien zawierać element CarUiRecyclerView
o identyfikatorze history_list
. CarUiRecyclerView
wyświetla elementy listy i opcjonalny nagłówek. Z aplikacji klienta można przekazać oba układy elementu listy i nagłówka. Jeśli jako headerResource ustawiona jest wartość Resources.ID_NULL
, nagłówek nie jest wyświetlany. Gdy PlaybackCardViewModel
zostanie przekazany do kontrolera, ten będzie monitorować LiveData<List<MediaSource>>
pobrany z playbackCardViewModel.getHistoryList()
.
public class PlaybackHistoryController {
public PlaybackHistoryController(
LifecycleOwner lifecycleOwner,
PlaybackCardViewModel playbackCardViewModel,
ViewGroup container,
@LayoutRes int itemResource,
@LayoutRes int headerResource,
int uxrConfigurationId) {
}
/**
* Renders the view.
*/
public void setupView() {
}
}
Układ każdego elementu powinien zawierać identyfikatory widoków danych, które chce wyświetlać, które odpowiadają klasom wewnętrznym ViewHolder
.
HistoryItemViewHolder(View itemView) {
super(itemView);
mContext = itemView.getContext();
mActiveView = itemView.findViewById(R.id.history_card_container_active);
mInactiveView = itemView.findViewById(R.id.history_card_container_inactive);
mMetadataTitleView = itemView.findViewById(R.id.history_card_title_active);
mAdditionalInfo = itemView.findViewById(R.id.history_card_subtitle_active);
mAppIcon = itemView.findViewById(R.id.history_card_app_thumbnail);
mAlbumArt = itemView.findViewById(R.id.history_card_album_art);
mAppTitleInactive = itemView.findViewById(R.id.history_card_app_title_inactive);
mAppIconInactive = itemView.findViewById(R.id.history_item_app_icon_inactive);
// ...
}
Aby wyświetlić listę historii na karcie multimedialnej utworzonej za pomocą klasy PlaybackCardController
(lub jej podklasy), możesz skonstruować obiekt PlaybackHistoryController
w konstruktorze klasy PlaybackCardController
.