Karta multimediów to samodzielna grupa ViewGroup, która wyświetla metadane multimediów, takie jak tytuł, okładka albumu i inne, oraz elementy sterujące odtwarzaniem, takie jak Odtwórz i Wstrzymaj, Pomiń, a nawet niestandardowe działania udostępniane przez aplikację multimedialną innej firmy. Karta multimediów może też wyświetlać kolejkę elementów multimedialnych, np. listę odtwarzania.
Rysunek 1. Przykładowe implementacje karty multimediów.
Jak karty multimediów są wdrażane w AAOS?
Grupy widoków, które wyświetlają informacje o mediach, obserwują aktualizacje LiveData z car-media-common
biblioteki, modelu danych, PlaybackViewModel
, aby wypełnić grupę widoków. Każda aktualizacja LiveData odpowiada podzbiorowi informacji o mediach, które uległy zmianie, np. MediaItemMetadata
, PlaybackStateWrapper
i MediaSource
.
Ponieważ takie podejście prowadzi do powtarzania kodu (każda aplikacja kliencka dodaje obserwatorów do każdego elementu LiveData, a wiele podobnych widoków otrzymuje zaktualizowane dane), stworzyliśmy PlaybackCardController
.
PlaybackCardController
PlaybackCardController
został dodany do biblioteki car-media-common
, aby ułatwić tworzenie karty multimedialnej. Jest to klasa publiczna, która jest tworzona za pomocą elementów ViewGroup (mView
), PlaybackViewModel (mDataModel
), PlaybackCardViewModel (mViewModel
) i instancji MediaItemsRepository
(mItemsRepository
).
W funkcji setupController
element ViewGroup jest analizowany pod kątem określonych widoków według identyfikatora, z mView.findViewById(R.id.xxx)
i przypisywany 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 wykonuje interakcje z widokami powiązanymi z otrzymanymi danymi. 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 klasy Controller 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);
}
}
Rozszerzanie klasy PlaybackCardController
Aplikacje klienckie, które chcą utworzyć kartę multimediów, powinny rozszerzyć klasę PlaybackCardController
, jeśli mają dodatkowe możliwości, które chcą obsługiwać w każdej aktualizacji LiveData. Obecni klienci w AAOS postępują zgodnie z tym wzorcem.
Najpierw należy utworzyć PlaybackCardController
podklasę, np. MediaCardController
. Następnie MediaCardController
powinien dodać statyczną 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
// ...
}
}
Utwórz instancję klasy PlaybackCardController lub jej podklasy.
Klasę kontrolera należy utworzyć w fragmencie lub działaniu, aby mieć właściciela cyklu życia dla obserwatorów LiveData.
mMediaCardController = (MediaCardController) new MediaCardController.Builder()
.setModels(mViewModel.getPlaybackViewModel(),
mViewModel,
mViewModel.getMediaItemsRepository())
.setViewGroup((ViewGroup) view)
.build();
mViewModel
to instancja klasy PlaybackCardViewModel
(lub podklasy).
PlaybackCardViewModel do zapisywania stanu
PlaybackCardViewModel
to ViewModel zapisujący stan, powiązany z fragmentem lub aktywnością. Należy go używać do odtwarzania zawartości karty multimedialnej w przypadku zmiany konfiguracji (np. przełączenia z jasnego na ciemny motyw, gdy użytkownik przejeżdża przez tunel). Domyślny PlaybackCardViewModel
obsługuje przechowywanie instancji MediaModel
do odtwarzania, z których można pobrać PlaybackViewModel
i MediaItemsRepository
. Użyj PlaybackCardViewModel
, aby śledzić stan kolejki, historię i menu przepełnienia za pomocą podanych metod pobierających i ustawiających.
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;
}
}
W razie potrzeby można rozszerzyć tę klasę, aby śledzić dodatkowe stany.
Wyświetlanie kolejki na karcie multimediów
PlaybackViewModel
udostępnia interfejsy LiveData API, które umożliwiają wykrywanie, czy MediaSource obsługuje kolejkę, oraz pobieranie listy obiektów MediaItemMetadata
w kolejce. Chociaż tych interfejsów API można używać bezpośrednio do wypełniania obiektu RecyclerView
informacjami o kolejce, do biblioteki car-media-common
dodano klasę PlaybackQueueController
, aby usprawnić ten proces. Układ każdego elementu w CarUiRecyclerView
jest określany przez aplikację klienta, podobnie jak opcjonalny układ nagłówka. Aplikacja kliencka może też ograniczyć liczbę elementów wyświetlanych w kolejce podczas jazdy, stosując niestandardowe ograniczenia UXR.
Konstruktor i funkcje ustawiające PlaybackQueueController
są pokazane w tym przykładzie. Zasoby układu queueResource
i headerResource
można przekazywać jako Resources.ID_NULL
, jeśli w pierwszym przypadku kontener zawiera już element CarUiRecyclerView
z atrybutem 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, które mają być wyświetlane, odpowiadające identyfikatorom 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 jej podklasy), klasę PlaybackQueueController
można utworzyć w konstruktorze PlaybackCardController
za pomocą klas mDataModel
i mItemsRepository
w przypadku instancji PlaybackViewModel
i MediaItemsRepository
.
Wyświetlanie historii wcześniej odtwarzanych źródeł multimediów
W tej sekcji dowiesz się, jak 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 umożliwia pobieranie listy historii multimediów. Zwraca LiveData zawierający listę obiektów MediaSource, które były wcześniej odtwarzane. Te dane można wykorzystać do wypełnienia obiektu CarUiRecyclerView
. Podobnie jak w przypadku PlaybackQueueController
, do biblioteki car-media-common
dodano klasę o nazwie PlaybackHistoryController
, aby uprościć ten proces.
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 powierzchni z kontrolerem PlaybackHistoryController
Użyj nowego parametru PlaybackHistoryController
, aby wypełnić dane historyczne w CarUiRecyclerView
. Konstruktory i główne funkcje tej klasy są następujące. Kontener przekazany z aplikacji klienta powinien zawierać element
CarUiRecyclerView
z identyfikatorem history_list
. Element CarUiRecyclerView
wyświetla elementy listy i opcjonalny nagłówek. Oba układy elementu listy i nagłówka mogą być przekazywane z aplikacji klienta. Jeśli jako headerResource ustawisz Resources.ID_NULL
, nagłówek nie będzie się wyświetlać. Po przekazaniu wartości PlaybackCardViewModel
do kontrolera monitoruje on wartość LiveData<List<MediaSource>>
pobraną 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, które mają być wyświetlane, odpowiadające identyfikatorom używanym w klasie wewnętrznej 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 multimediów utworzonej za pomocą klasy PlaybackCardController
(lub jej podklasy), obiekt PlaybackHistoryController
można utworzyć w konstruktorze klasy PlaybackCardController
.