媒體資訊卡是獨立的 ViewGroup,可顯示媒體中繼資料 (例如標題、專輯封面等),以及「播放」和「暫停」、「跳過」等播放控制選項,甚至第三方媒體應用程式提供的自訂動作。媒體資訊卡也可以顯示媒體項目佇列,例如播放清單。
圖 1. 媒體資訊卡實作範例。
如何在 AAOS 中實作媒體資訊卡?
顯示媒體資訊的 ViewGroup 會監控car-media-common
媒體庫 資料模型 (即 PlaybackViewModel
) 的 LiveData 更新,以填入 ViewGroup。每次更新 LiveData 時,都會提供已變更的媒體資訊子集,例如 MediaItemMetadata
、PlaybackStateWrapper
和 MediaSource
。
因為這種做法會導致程式碼重複 (每個用戶端應用程式都會在每個 LiveData 片段上新增觀察器,且許多類似的檢視區塊都會指派更新的資料),因此我們建立了 PlaybackCardController
。
PlaybackCardController
PlaybackCardController
已新增至car-media-common
媒體庫,可協助建立媒體資訊卡。這是使用 ViewGroup (mView
)、PlaybackViewModel (mDataModel
)、PlaybackCardViewModel (mViewModel
) 和 MediaItemsRepository
執行個體 (mItemsRepository
) 建構的公開類別。
在 setupController
函式中,系統會依 ID 剖析 ViewGroup 的特定檢視區塊,並將 mView.findViewById(R.id.xxx)
指派給受保護的 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);
// ...
}
系統會在受保護的方法中觀察 PlaybackViewModel
的每次 LiveData 更新,並與收到的資料相關的 View 互動。舉例來說,MediaItemMetadata
的觀察器會在 mTitle
TextView
上設定標題,並將 MediaItemMetadata.ArtworkRef
傳遞至專輯封面 ImageBinder
mAlbumArtBinder
。如果中繼資料為空值,系統會隱藏檢視畫面。如有需要,Controller 的子類別可以覆寫這項邏輯。
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
如要建立媒體資訊卡,用戶端應用程式應擴充 PlaybackCardController
,以便在每次 LiveData 更新時處理其他功能。AAOS 的現有用戶端會遵循這個模式。
首先,應建立 PlaybackCardController
子類別,例如 MediaCardController
。接著,MediaCardController
應新增靜態內部 Builder 類別,該類別會擴充 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
// ...
}
}
例項化 PlaybackCardController 或子類別
您應從 Fragment 或 Activity 例項化 Controller 類別,以便為 LiveData 觀察器提供 LifecycleOwner。
mMediaCardController = (MediaCardController) new MediaCardController.Builder()
.setModels(mViewModel.getPlaybackViewModel(),
mViewModel,
mViewModel.getMediaItemsRepository())
.setViewGroup((ViewGroup) view)
.build();
mViewModel
是 PlaybackCardViewModel
(或子類別) 的執行個體。
PlaybackCardViewModel,用於儲存狀態
PlaybackCardViewModel
是與 Fragment 或 Activity 繫結的狀態儲存 ViewModel,如果發生設定變更 (例如使用者開車通過隧道時從淺色主題切換為深色主題),就應使用這個 ViewModel 重建媒體資訊卡的內容。預設 PlaybackCardViewModel
會處理 MediaModel
執行個體的儲存作業,以供播放,並從中擷取 PlaybackViewModel
和 MediaItemsRepository
。使用 PlaybackCardViewModel
透過提供的 getter 和 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;
}
}
如果需要追蹤其他狀態,可以擴充這個類別。
在媒體資訊卡中顯示佇列
PlaybackViewModel
提供 LiveData API,可偵測 MediaSource 是否支援佇列,並擷取佇列中的 MediaItemMetadata
物件清單。雖然可以直接使用這些 API,以佇列資訊填入 RecyclerView
物件,但 car-media-common
程式庫已新增 PlaybackQueueController
類別,可簡化這個程序。CarUiRecyclerView
中每個項目的版面配置,是由用戶端應用程式和選用的標題版面配置指定。用戶端應用程式也可以選擇透過自訂 UXR 限制,在駕駛狀態下限制佇列中顯示的項目數量。
下列範例顯示 PlaybackQueueController
建構函式和設定程式。如果容器已包含具有 id queue_list
的 CarUiRecyclerView
,則可以將 queueResource
和 headerResource
版面配置資源做為 Resources.ID_NULL
傳遞;如果佇列沒有標頭,則可以將 headerResource
版面配置資源做為 Resources.ID_NULL
傳遞。
/**
* 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;
}
每個佇列項目的版面配置應包含要顯示的檢視區塊 ID,這些 ID 對應於 QueueViewHolder
內部類別中使用的 ID。
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);
// ...
}
如要在使用 PlaybackCardController
(或子類別) 建立的媒體資訊卡中顯示佇列,可以在 PlaybackCardController
建構函式中使用 mDataModel
和 mItemsRepository
,分別建構 PlaybackViewModel
和 MediaItemsRepository
執行個體。PlaybackQueueController
顯示先前播放的 MediaSource 記錄
本節將說明如何顯示先前播放的媒體來源記錄。
使用 PlaybackCardViewModel API 取得記錄清單
PlaybackCardViewModel
提供名為 getHistoryList()
的 LiveData API,可擷取媒體記錄清單。這個函式會傳回 LiveData,其中包含先前播放的 MediaSource 清單。這項資料可用於填入 CarUiRecyclerView
物件。與 PlaybackQueueController
類似,car-media-common
程式庫已新增名為 PlaybackHistoryController
的類別,可簡化程序。
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;
}
}
使用 PlaybackHistoryController 的 Surface 記錄 UI
使用新的 PlaybackHistoryController
,將歷來資料填入 CarUiRecyclerView
。這個類別的建構函式和主要函式如下。從用戶端應用程式傳遞的容器應包含 ID 為 history_list
的 CarUiRecyclerView
。CarUiRecyclerView
會顯示清單項目和選用標題。清單項目和標題的版面配置都可以從用戶端應用程式傳遞。如果 Resources.ID_NULL
設為 headerResource,系統就不會顯示標題。將 PlaybackCardViewModel
傳遞至控制器後,系統會監控從 playbackCardViewModel.getHistoryList()
擷取的 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() {
}
}
每個項目的版面配置應包含要顯示的檢視區塊 ID,這些 ID 對應於 ViewHolder
內部類別中使用的 ID。
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);
// ...
}
如要在使用 PlaybackCardController
(或子類別) 建立的媒體資訊卡中顯示記錄清單,可以在 PlaybackCardController
的建構函式中建構 PlaybackHistoryController
。