כרטיס מדיה הוא ViewGroup עצמאי שמוצגים בו מטא-נתונים של מדיה, כמו השם, תמונת האלבום ועוד, וגם אמצעי בקרה על ההפעלה, כמו הפעלה, השהיה, דילוג ואפילו פעולות בהתאמה אישית שמספקת אפליקציית המדיה של הצד השלישי. בכרטיס מדיה יכולה להופיע גם תור של פריטים של מדיה, כמו פלייליסט.
איור 1. הטמעות לדוגמה של כרטיסי מדיה.
איך כרטיסי מדיה מיושמים ב-AAOS?
כדי לאכלס את ViewGroup, קבוצות ViewGroup שמוצג בהן מידע על מדיה עוקבות אחרי עדכוני LiveData מהמודל PlaybackViewModel
של הנתונים בספרייה car-media-common
. כל עדכון של LiveData תואם לקבוצת משנה של פרטי המדיה שהשתנו, כמו MediaItemMetadata
, PlaybackStateWrapper
ו-MediaSource
.
הגישה הזו מובילה לקוד חוזר (כל אפליקציית לקוח מוסיפה משגיחים לכל חלק של LiveData, ונתונים מעודכנים מוקצים להרבה תצוגות דומות), לכן יצרנו את PlaybackCardController
.
PlaybackCardController
ה-PlaybackCardController
נוסף לספרייה car-media-common
כדי לעזור לך ליצור כרטיס מדיה. זוהי כיתה ציבורית שנוצרת באמצעות ViewGroup (mView
), PlaybackViewModel (mDataModel
), PlaybackCardViewModel (mViewModel
) ומכונה של MediaItemsRepository
(mItemsRepository
).
בפונקציה setupController
, המערכת מנתחת את 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);
// ...
}
כל עדכון של LiveData מה-PlaybackViewModel
מתבצע בשיטה מוגנת, ומבצע אינטראקציות עם התצוגות הרלוונטיות לנתונים שהתקבלו. לדוגמה, משתמש שמתבונן ב-MediaItemMetadata
מגדיר את השם ב-mTitle
TextView
ומעביר את MediaItemMetadata.ArtworkRef
לתמונת האלבום ImageBinder
mAlbumArtBinder
. אם המטא-נתונים הם null, התצוגות מוסתרות. אם צריך, ניתן לשנות את הלוגיקה הזו באמצעות מחלקות משנה של הבקר.
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 או של תת-מחלקה
צריך ליצור מופע של מחלקת Controller מ-Fragment או מ-Activity כדי שיהיה LifecycleOwner למעקבים אחרי LiveData.
mMediaCardController = (MediaCardController) new MediaCardController.Builder()
.setModels(mViewModel.getPlaybackViewModel(),
mViewModel,
mViewModel.getMediaItemsRepository())
.setViewGroup((ViewGroup) view)
.build();
mViewModel
הוא מופע של PlaybackCardViewModel
(או של תת-הסוג).
PlaybackCardViewModel לשמירת מצב
PlaybackCardViewModel
הוא ViewModel לשמירת מצב שמקושר ל-Fragment או ל-Activity, שבו צריך להשתמש כדי לשחזר את התוכן של כרטיס המדיה אם מתרחש שינוי בהגדרות (למשל, מעבר מעיצוב בהיר לעיצוב כהה כשהמשתמש נוסע במנהרה). ברירת המחדל PlaybackCardViewModel
מטפלת באחסון של המופעים של MediaModel
להפעלה, שמהם אפשר לאחזר את PlaybackViewModel
ו-MediaItemsRepository
. אפשר להשתמש ב-PlaybackCardViewModel
כדי לעקוב אחרי המצב של התור, ההיסטוריה ותפריט הoverflow באמצעות ה-getters וה-setters שסופקו.
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
מספק ממשקי API של LiveData כדי לזהות אם MediaSource תומך בתור, ולאחזר את רשימת האובייקטים מסוג MediaItemMetadata
בתור. אפשר להשתמש בממשקי ה-API האלה ישירות כדי לאכלס אובייקט RecyclerView
במידע מהתור, אבל כדי לייעל את התהליך הזה נוספה לכיתה PlaybackQueueController
בספרייה car-media-common
. פריסת כל פריט ב-CarUiRecyclerView
מצוינה על ידי אפליקציית הלקוח, וגם פריסת כותרת אופציונלית. אפליקציית הלקוח יכולה גם להגביל את מספר הפריטים שמוצגים בתור במצב 'נהיגה' באמצעות הגבלות UXR בהתאמה אישית.
המבנה הגנרטיבי וה-setters של PlaybackQueueController
מוצגים בדוגמה הבאה. אפשר להעביר את משאבי הפריסה queueResource
ו-headerResource
בתור Resources.ID_NULL
אם, במקרה הראשון, המאגר כבר מכיל CarUiRecyclerView
עם id queue_list
, ובמקרה השני, אין בתור Header.
/**
* 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;
}
הפריסה של כל פריט בתור צריכה להכיל את המזהים של התצוגות המפורטות שרוצים להציג, שתואמים לאלה שבשימוש בכיתה הפנימית 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);
// ...
}
כדי להציג תור בכרטיס מדיה שנוצר באמצעות PlaybackCardController
(או באמצעות תת-מחלקה), אפשר ליצור את PlaybackQueueController
ב-constructor של PlaybackCardController
באמצעות mDataModel
ו-mItemsRepository
למופעים של PlaybackViewModel
ו-MediaItemsRepository
, בהתאמה.
הצגת ההיסטוריה של מקורות המדיה שהושמעו בעבר
בקטע הזה תלמדו איך להציג את ההיסטוריה של מקורות המדיה שהפעלתם בעבר.
אחזור רשימת ההיסטוריה באמצעות PlaybackCardViewModel API
PlaybackCardViewModel
מספק ממשק LiveData API שנקרא getHistoryList()
כדי לאחזר את רשימת היסטוריית המדיה. הפונקציה מחזירה LiveData שמכיל רשימה של מקורות מדיה שהופעלו בעבר. אפשר להשתמש בנתונים האלה כדי לאכלס אובייקט 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;
}
}
ממשק המשתמש של היסטוריית Surface באמצעות PlaybackHistoryController
אפשר להשתמש ב-PlaybackHistoryController
החדש כדי לאכלס את נתוני ההיסטוריה ב-CarUiRecyclerView
. ה-constructors והפונקציות הראשיות של הכיתה הזו הם: הקונטיינר שמוענק מאפליקציית הלקוח צריך לכלול CarUiRecyclerView
עם המזהה history_list
. ב-CarUiRecyclerView
מוצגים פריטי הרשימה וכותרת אופציונלית. אפשר להעביר מאפליקציית הלקוח את שני הפריסות – של פריט הרשימה ושל הכותרת. אם Resources.ID_NULL
מוגדר כ-headerResource, הכותרת לא תוצג. אחרי שה-PlaybackCardViewModel
מועבר למסוף הבקרה, הוא עוקב אחרי ה-LiveData<List<MediaSource>>
שאוחזר מ-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() {
}
}
הפריסה של כל פריט צריכה להכיל את המזהים של התצוגות המפורטות שרוצים להציג, שתואמים לאלה שבשימוש בכיתה הפנימית 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);
// ...
}
כדי להציג רשימת היסטוריה בכרטיס מדיה שנוצר באמצעות PlaybackCardController
(או באמצעות תת-מחלקה), אפשר ליצור את PlaybackHistoryController
ב-constructor של PlaybackCardController
.