Una scheda multimediale è un ViewGroup autonomo che mostra i metadati multimediali, ad esempio il titolo, la copertina dell'album e altro ancora, e i controlli di riproduzione, come Riproduci e Pausa,Salta e persino azioni personalizzate fornite dall'app multimediale di terze parti. Una scheda multimediale può anche mostrare una coda di elementi multimediali, ad esempio una playlist.
Figura 1. Implementazioni di esempio della scheda multimediale.
Come vengono implementate le schede multimediali in AAOS?
I ViewGroups che mostrano le informazioni sui contenuti multimediali osservano gli aggiornamenti di LiveData dal modello di dati della libreria car-media-common
, PlaybackViewModel
, per popolare il ViewGroup. Ogni aggiornamento di LiveData corrisponde a un sottoinsieme di informazioni sui contenuti multimediali
che sono cambiate, ad esempio MediaItemMetadata
, PlaybackStateWrapper
e
MediaSource
.
Poiché questo approccio porta a codice ripetuto (ogni app client aggiunge osservatori a
ogni elemento LiveData e a molte View simili vengono assegnati i dati aggiornati), abbiamo
creato PlaybackCardController
.
PlaybackCardController
L'PlaybackCardController
è stato aggiunto alla raccolta car-media-common
per
aiutarti a creare una scheda multimediale. Si tratta di una classe pubblica creata con
un ViewGroup (mView
), un PlaybackViewModel (mDataModel
), un PlaybackCardViewModel
(mViewModel
) e un'istanza MediaItemsRepository
(mItemsRepository
).
Nella funzione setupController
, il ViewGroup viene analizzato per determinate visualizzazioni in base
all'ID, con mView.findViewById(R.id.xxx)
e assegnato a oggetti View protetti.
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);
// ...
}
Ogni aggiornamento di LiveData da PlaybackViewModel
viene osservato in un metodo protetto ed esegue interazioni con le View pertinenti ai dati ricevuti. Ad esempio, un osservatore su MediaItemMetadata
imposta il titolo su
mTitle
TextView
e passa MediaItemMetadata.ArtworkRef
alla copertina
dell'album ImageBinder
mAlbumArtBinder
. Se i metadati sono nulli, le visualizzazioni
sono nascoste. Le sottoclassi del controller possono ignorare questa logica, se necessario.
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);
}
}
Estendi PlaybackCardController
Le app client che vogliono creare una scheda multimediale devono estendere
PlaybackCardController
se hanno funzionalità aggiuntive che vogliono
gestire in ogni aggiornamento di LiveData. I client esistenti in AAOS seguono questo pattern.
Innanzitutto, deve essere creata una sottoclasse PlaybackCardController
, ad esempio MediaCardController
. Successivamente, MediaCardController
deve aggiungere una classe Builder interna statica che estende quella di 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
// ...
}
}
Crea un'istanza di PlaybackCardController o di una sottoclasse
La classe Controller deve essere istanziata da un frammento o da un'attività per avere un LifecycleOwner per gli osservatori LiveData.
mMediaCardController = (MediaCardController) new MediaCardController.Builder()
.setModels(mViewModel.getPlaybackViewModel(),
mViewModel,
mViewModel.getMediaItemsRepository())
.setViewGroup((ViewGroup) view)
.build();
mViewModel
è un'istanza di PlaybackCardViewModel
(o sottoclasse).
PlaybackCardViewModel per salvare lo stato
PlaybackCardViewModel
è un ViewModel che salva lo stato associato a un fragment o a un'attività che deve essere utilizzato per ricostruire i contenuti della scheda multimediale se si verifica una modifica della configurazione (ad esempio il passaggio dal tema chiaro a quello scuro quando un utente guida in una galleria). Il PlaybackCardViewModel
predefinito gestisce
l'archiviazione delle istanze di MediaModel
per la riproduzione, da cui è possibile recuperare
PlaybackViewModel
e MediaItemsRepository
. Utilizza
PlaybackCardViewModel
per monitorare lo stato della coda, della cronologia e del menu
overflow tramite i metodi getter e setter forniti.
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;
}
}
Questa classe può essere estesa se è necessario monitorare altri stati.
Mostrare una coda in una scheda multimediale
PlaybackViewModel
fornisce API LiveData per rilevare se MediaSource
supporta una coda e per recuperare l'elenco di oggetti MediaItemMetadata
nella
coda. Sebbene queste API possano essere utilizzate direttamente per compilare un oggetto RecyclerView
con le informazioni sulla coda, è stata aggiunta una classe PlaybackQueueController
alla libreria car-media-common
per semplificare questo processo. Il layout
di ogni elemento in CarUiRecyclerView
viene specificato dall'app client, così come
un layout di intestazione facoltativo. L'app client può anche scegliere di limitare il numero di
elementi mostrati nella coda durante la guida con limitazioni UXR personalizzate.
Il costruttore PlaybackQueueController
e i setter sono mostrati nel seguente
esempio. Le risorse di layout queueResource
e headerResource
possono essere trasmesse
come Resources.ID_NULL
se, nel primo caso, il contenitore contiene già un
CarUiRecyclerView
con id queue_list
e, nel secondo caso, la coda
non ha un'intestazione.
/**
* 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;
}
Il layout di ogni elemento della coda deve contenere gli ID delle visualizzazioni che vuole mostrare e che corrispondono a quelli utilizzati nella classe interna 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);
// ...
}
Per mostrare una coda in una scheda multimediale creata con PlaybackCardController
(o una sottoclasse), PlaybackQueueController
può essere costruito nel
costruttore PlaybackCardController
utilizzando mDataModel
e mItemsRepository
per le istanze PlaybackViewModel
e MediaItemsRepository
, rispettivamente.
Mostra la cronologia delle MediaSource riprodotte in precedenza
In questa sezione scoprirai come mostrare e visualizzare la cronologia delle origini multimediali riprodotte in precedenza.
Ottenere l'elenco della cronologia con l'API PlaybackCardViewModel
PlaybackCardViewModel
fornisce un'API LiveData chiamata getHistoryList()
per
recuperare l'elenco della cronologia dei contenuti multimediali. Restituisce un LiveData contenente un elenco di
MediaSource riprodotti in precedenza. Questi dati possono essere utilizzati per compilare
un oggetto CarUiRecyclerView
. Simile a PlaybackQueueController
, una classe
denominata PlaybackHistoryController
è stata aggiunta alla libreria
car-media-common
per semplificare la procedura.
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;
}
}
UI della cronologia di Surface con PlaybackHistoryController
Utilizza il nuovo PlaybackHistoryController
per compilare i dati della cronologia
in un CarUiRecyclerView
. I costruttori e le funzioni principali di questa classe sono
i seguenti. Il contenitore passato dall'app client deve contenere un
CarUiRecyclerView
con l'ID history_list
. CarUiRecyclerView
mostra gli elementi dell'elenco e un'intestazione facoltativa. Entrambi i layout per la voce di elenco
e l'intestazione possono essere passati dall'app client. Se Resources.ID_NULL
è impostato
come headerResource, l'intestazione non viene mostrata. Dopo che il
PlaybackCardViewModel
viene passato al controller, questo monitora il
LiveData<List<MediaSource>>
recuperato da
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() {
}
}
Il layout di ogni elemento deve contenere gli ID delle visualizzazioni che vuole
mostrare che corrispondono a quelli utilizzati nella classe interna 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);
// ...
}
Per mostrare un elenco della cronologia in una scheda multimediale creata con
PlaybackCardController
(o una sottoclasse), PlaybackHistoryController
può essere
costruito nel costruttore di PlaybackCardController
.