Eine Medienkarte ist eine in sich geschlossene ViewGroup, in der Medienmetadaten wie Titel, Albumcover usw. angezeigt werden. Außerdem werden Wiedergabesteuerelemente wie Wiedergeben, Pause und Überspringen sowie benutzerdefinierte Aktionen der Drittanbieter-Media-App angezeigt. Auf einer Medienkarte kann auch eine Warteschlange mit Media-Elementen wie einer Playlist angezeigt werden.
Abbildung 1: Beispielimplementierungen für Media Cards.
Wie werden Media-Karten in AAOS implementiert?
ViewGroups, in denen Medieninformationen angezeigt werden, beobachten LiveData-Aktualisierungen aus dem car-media-common
-Bibliotheksdatenmodell data, dem PlaybackViewModel
, um die ViewGroup zu füllen. Jede LiveData-Aktualisierung entspricht einer Teilmenge der geänderten Medieninformationen, z. B. MediaItemMetadata
, PlaybackStateWrapper
und MediaSource
.
Da dieser Ansatz zu sich wiederholendem Code führt (jede Client-App fügt Observer für jede LiveData-Komponente hinzu und vielen ähnlichen Views werden die aktualisierten Daten zugewiesen), haben wir die PlaybackCardController
erstellt.
PlaybackCardController
Die PlaybackCardController
wurde der car-media-common
-Bibliothek hinzugefügt, um die Erstellung einer Media-Karte zu erleichtern. Dies ist eine öffentliche Klasse, die mit einer ViewGroup (mView
), einem PlaybackViewModel (mDataModel
), einem PlaybackCardViewModel (mViewModel
) und einer MediaItemsRepository
-Instanz (mItemsRepository
) erstellt wird.
In der Funktion setupController
wird die ViewGroup nach bestimmten Ansichten nach ID geparst, mit mView.findViewById(R.id.xxx)
und geschützten View-Objekten zugewiesen.
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);
// ...
}
Jede LiveData-Aktualisierung von PlaybackViewModel
wird in einer geschützten Methode beobachtet und es werden Interaktionen mit den für die empfangenen Daten relevanten Ansichten ausgeführt. Ein Observer für MediaItemMetadata
legt beispielsweise den Titel für mTitle
TextView
fest und übergibt MediaItemMetadata.ArtworkRef
an das Albumcover ImageBinder
mAlbumArtBinder
. Wenn die Metadaten „null“ sind, werden die Ansichten ausgeblendet. Unterklassen des Controllers können diese Logik bei Bedarf überschreiben.
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);
}
}
PlaybackCardController erweitern
Client-Apps, die eine Media-Karte erstellen möchten, sollten die PlaybackCardController
erweitern, wenn sie zusätzliche Funktionen haben, die sie bei jeder LiveData-Aktualisierung ausführen möchten. Bestehende Clients in AAOS folgen diesem Muster.
Zuerst muss eine PlaybackCardController
-Unterklasse erstellt werden, z. B. MediaCardController
. Als Nächstes sollte die MediaCardController
eine statische innere Builder-Klasse hinzufügen, die die der PlaybackCardController
erweitert.
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
// ...
}
}
PlaybackCardController oder eine Unterklasse instanziieren
Die Controller-Klasse sollte von einem Fragment oder einer Aktivität instanziiert werden, damit ein LifecycleOwner für die LiveData-Beobachter vorhanden ist.
mMediaCardController = (MediaCardController) new MediaCardController.Builder()
.setModels(mViewModel.getPlaybackViewModel(),
mViewModel,
mViewModel.getMediaItemsRepository())
.setViewGroup((ViewGroup) view)
.build();
mViewModel
ist eine Instanz von PlaybackCardViewModel
(oder einer abgeleiteten Klasse).
PlaybackCardViewModel zum Speichern des Status
PlaybackCardViewModel
ist ein ViewModel zum Speichern des Status, das an ein Fragment oder eine Aktivität gebunden ist. Es sollte verwendet werden, um den Inhalt der Media-Karte wiederherzustellen, wenn sich die Konfiguration ändert, z. B. wenn ein Nutzer durch einen Tunnel fährt und vom hellen zum dunklen Design gewechselt wird. Die Standard-PlaybackCardViewModel
übernimmt das Speichern von Instanzen der MediaModel
s für die Wiedergabe, aus denen die PlaybackViewModel
und MediaItemsRepository
abgerufen werden können. Verwenden Sie PlaybackCardViewModel
, um den Status der Warteschlange, des Verlaufs und des Überlaufmenüs über die bereitgestellten Getters und Setters zu verfolgen.
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;
}
}
Diese Klasse kann erweitert werden, wenn zusätzliche Status erfasst werden müssen.
Wiedergabeliste auf einer Medienkarte anzeigen
Die PlaybackViewModel
bietet LiveData-APIs, mit denen erkannt werden kann, ob die MediaSource eine Warteschlange unterstützt, und mit denen die Liste der MediaItemMetadata
-Objekte in der Warteschlange abgerufen werden kann. Diese APIs können zwar direkt verwendet werden, um ein RecyclerView
-Objekt mit den Warteschlangeninformationen zu füllen, aber der car-media-common
-Bibliothek wurde eine PlaybackQueueController
-Klasse hinzugefügt, um diesen Vorgang zu optimieren. Das Layout für jedes Element in CarUiRecyclerView
wird von der Client-App sowie durch ein optionales Header-Layout angegeben. Die Client-App kann auch die Anzahl der Elemente in der Warteschlange während der Fahrt mit benutzerdefinierten UXR-Einschränkungen begrenzen.
Der PlaybackQueueController
-Konstruktor und die Setter werden im folgenden Beispiel gezeigt. Die Layoutressourcen queueResource
und headerResource
können als Resources.ID_NULL
übergeben werden, wenn der Container im ersten Fall bereits ein CarUiRecyclerView
mit id queue_list
enthält und die Warteschlange im zweiten Fall keinen Header hat.
/**
* 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;
}
Das Layout für jedes Warteschlangenelement sollte die IDs für die Ansichten enthalten, die angezeigt werden sollen und die den in der inneren Klasse QueueViewHolder
verwendeten IDs entsprechen.
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);
// ...
}
Wenn Sie eine Warteschlange in einer mit PlaybackCardController
(oder einer Unterklasse) erstellten Media-Karte anzeigen möchten, kann die PlaybackQueueController
im PlaybackCardController
-Konstruktor mit mDataModel
und mItemsRepository
für die PlaybackViewModel
- bzw. MediaItemsRepository
-Instanzen erstellt werden.
Verlauf der zuvor wiedergegebenen MediaSources anzeigen
In diesem Abschnitt erfahren Sie, wie Sie den Verlauf zuvor wiedergegebener Media-Quellen anzeigen lassen.
Verlaufsliste mit der PlaybackCardViewModel API abrufen
PlaybackCardViewModel
bietet eine LiveData API namens getHistoryList()
, mit der die Liste des Media-Verlaufs abgerufen werden kann. Es wird ein LiveData-Objekt mit einer Liste von MediaSources zurückgegeben, die zuvor wiedergegeben wurden. Mit diesen Daten kann ein CarUiRecyclerView
-Objekt gefüllt werden. Ähnlich wie bei PlaybackQueueController
wurde der car-media-common
-Bibliothek eine Klasse mit dem Namen PlaybackHistoryController
hinzugefügt, um den Prozess zu optimieren.
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;
}
}
Benutzeroberfläche des Verlaufs mit PlaybackHistoryController
Mit dem neuen PlaybackHistoryController
können Sie Verlaufsdaten in eine CarUiRecyclerView
einfügen. Die Konstruktoren und Hauptfunktionen dieser Klasse sind: Der von der Client-App übergebene Container sollte ein CarUiRecyclerView
mit der ID history_list
enthalten. Im CarUiRecyclerView
werden die Listenelemente und eine optionale Überschrift angezeigt. Sowohl das Layout für das Listenelement als auch das Layout für den Header können von der Client-App übergeben werden. Wenn Resources.ID_NULL
als „headerResource“ festgelegt ist, wird der Header nicht angezeigt. Nachdem PlaybackCardViewModel
an den Controller übergeben wurde, wird LiveData<List<MediaSource>>
überwacht, das aus playbackCardViewModel.getHistoryList()
abgerufen wurde.
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() {
}
}
Das Layout für jedes Element sollte die IDs für die Ansichten enthalten, die angezeigt werden sollen und die den in der inneren Klasse ViewHolder
verwendeten IDs entsprechen.
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);
// ...
}
Wenn Sie in einer mit PlaybackCardController
(oder einer Unterklasse) erstellten Media-Karte eine Verlaufsliste anzeigen möchten, kann PlaybackHistoryController
im Konstruktor von PlaybackCardController
erstellt werden.