Eine Mediakarte ist eine eigenständige ViewGroup, die unter anderem Medienmetadaten wie den Titel und das Albumcover anzeigt. Außerdem werden Wiedergabesteuerungen wie Wiedergabe, Pause und Überspringen sowie benutzerdefinierte Aktionen angezeigt, die von der Drittanbieter-Media-App bereitgestellt werden. Eine Mediakarte kann auch eine Warteschlange mit Medienelementen wie einer Playlist enthalten.
Abbildung 1. Beispielimplementierungen für Media Cards
Wie werden Mediacards in AAOS implementiert?
Für ViewGroups, die Medieninformationen enthalten, werden LiveData-Aktualisierungen aus dem PlaybackViewModel
-Modell (car-media-common
-Bibliothek mit Daten) verwendet, um die ViewGroup zu füllen. Jedes LiveData-Update entspricht einer Teilmenge der Medieninformationen, die sich geändert haben, z. B. MediaItemMetadata
, PlaybackStateWrapper
und MediaSource
.
Da dieser Ansatz zu wiederholtem Code führt (jede Client-App fügt Beobachter zu jedem LiveData-Element hinzu und vielen ähnlichen Ansichten werden die aktualisierten Daten zugewiesen), haben wir die PlaybackCardController
erstellt.
PlaybackCardController
Die PlaybackCardController
wurde der car-media-common
-Mediathek hinzugefügt, um beim Erstellen einer Medienkarte zu helfen. Dies ist eine öffentliche Klasse, die mit einer ViewGroup (mView
), PlaybackViewModel (mDataModel
), PlaybackCardViewModel (mViewModel
) und einer MediaItemsRepository
-Instanz (mItemsRepository
) erstellt wird.
In der Funktion setupController
wird die ViewGroup anhand der ID mit mView.findViewById(R.id.xxx)
nach bestimmten Ansichten geparst 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 der PlaybackViewModel
wird in einer geschützten Methode beobachtet und es werden Interaktionen mit den Ansichten ausgeführt, die für die empfangenen Daten relevant sind. Ein Beobachter auf MediaItemMetadata
legt beispielsweise den Titel auf dem mTitle
TextView
fest und übergibt den 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 Medienkarte erstellen möchten, sollten PlaybackCardController
erweitern, wenn sie zusätzliche Funktionen haben, die bei jeder LiveData-Aktualisierung verarbeitet werden sollen. Bestehende Kunden in AAOS folgen diesem Muster.
Zuerst muss eine PlaybackCardController
-Unterklasse erstellt werden, z. B. die 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 über ein Fragment oder eine Aktivität instanziiert werden, um einen LifecycleOwner für die LiveData-Beobachter zu haben.
mMediaCardController = (MediaCardController) new MediaCardController.Builder()
.setModels(mViewModel.getPlaybackViewModel(),
mViewModel,
mViewModel.getMediaItemsRepository())
.setViewGroup((ViewGroup) view)
.build();
mViewModel
ist eine Instanz der PlaybackCardViewModel
(oder einer abgeleiteten Klasse).
PlaybackCardViewModel zum Speichern des Status
PlaybackCardViewModel
ist ein ViewModel zum Speichern des Status, das mit einem Fragment oder einer Aktivität verknüpft ist. Es sollte verwendet werden, um den Inhalt der Medienkarte bei einer Konfigurationsänderung wiederherzustellen, z. B. beim Wechsel vom hellen zum dunklen Design, wenn ein Nutzer durch einen Tunnel fährt. Der Standard-PlaybackCardViewModel
kümmert sich um das Speichern von Instanzen der MediaModel
s zur Wiedergabe, aus denen die PlaybackViewModel
und MediaItemsRepository
abgerufen werden können. Mit PlaybackCardViewModel
können Sie den Status der Warteschlange, des Verlaufs und des Überlaufmenüs über die bereitgestellten Getter und Setter 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, um zu erkennen, ob die MediaSource eine Warteschlange unterstützt, und um die Liste der MediaItemMetadata
-Objekte in der Warteschlange abzurufen. 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 der CarUiRecyclerView
wird von der Client-App sowie als optionales Header-Layout angegeben. Die Client-App kann auch die Anzahl der Elemente begrenzen, die im Drive-Status in der Warteschlange angezeigt werden, indem benutzerdefinierte UXR-Einschränkungen festgelegt werden.
Der Konstruktor und die Setter von PlaybackQueueController
sind im folgenden Beispiel zu sehen. Die Layoutressourcen queueResource
und headerResource
können als Resources.ID_NULL
übergeben werden, wenn der Container im ersten Fall bereits eine 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 Warteelement sollte die IDs für die anzuzeigenden Ansichten enthalten, die denen in der inneren QueueViewHolder
-Klasse 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 du eine Warteschlange in einer Medienkarte anzeigen möchtest, die mit der PlaybackCardController
(oder einer Unterklasse) erstellt wurde, kannst du die PlaybackQueueController
im Konstruktor der PlaybackCardController
mit mDataModel
und mItemsRepository
für die PlaybackViewModel
- und MediaItemsRepository
-Instanzen erstellen.
Verlauf der zuvor abgespielten Medienquellen anzeigen
In diesem Abschnitt erfahren Sie, wie Sie den Verlauf der zuvor abgespielten Medienquellen anzeigen und präsentieren.
Wiedergabeliste mit der PlaybackCardViewModel API abrufen
PlaybackCardViewModel
bietet eine LiveData API namens getHistoryList()
, um die Liste der Medienverläufe abzurufen. Es gibt LiveData zurück, die eine Liste der MediaSources enthält, die zuvor wiedergegeben wurden. Diese Daten können zum Ausfüllen eines CarUiRecyclerView
-Objekts verwendet werden. Ähnlich wie bei PlaybackQueueController
wurde der car-media-common
-Bibliothek die Klasse 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 für den Wiedergabeverlauf mit PlaybackHistoryController
Verwenden Sie das neue PlaybackHistoryController
, um die Verlaufsdaten in einem CarUiRecyclerView
zu füllen. Die Konstruktoren und Hauptfunktionen dieser Klasse sind: Der von der Clientanwendung übergebene Container sollte eine 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, überwacht er die aus playbackCardViewModel.getHistoryList()
abgerufenen LiveData<List<MediaSource>>
.
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 anzuzeigenden Ansichten enthalten, die denen in der inneren ViewHolder
-Klasse 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 in einer mit der PlaybackCardController
(oder einer Unterklasse) erstellten Medienkarte eine Verlaufsliste angezeigt werden soll, kann die PlaybackHistoryController
im Konstruktor der PlaybackCardController
erstellt werden.