การ์ดสื่อคือ ViewGroup แบบสแตนด์อโลนที่แสดงข้อมูลเมตาของสื่อ เช่น ชื่อ หน้าปกอัลบั้ม และอื่นๆ รวมถึงแสดงตัวควบคุมการเล่น เช่น เล่นและหยุดชั่วคราว ข้าม และการดำเนินการที่กำหนดเองซึ่งแอปสื่อของบุคคลที่สามมีให้ นอกจากนี้ การ์ดสื่อยังแสดงคิวของรายการสื่อ เช่น เพลย์ลิสต์ ได้อีกด้วย
รูปที่ 1 ตัวอย่างการใช้งานการ์ดสื่อ
การใช้การ์ดสื่อใน AAOS เป็นอย่างไร
ViewGroup ที่แสดงข้อมูลสื่อจะสังเกตการอัปเดต LiveData จากPlaybackViewModel
ซึ่งเป็นโมเดล data ของcar-media-common
คลังเพื่อป้อนข้อมูลใน PlaybackViewModel
การอัปเดต LiveData แต่ละรายการสอดคล้องกับข้อมูลสื่อชุดย่อยที่มีการเปลี่ยนแปลง เช่น MediaItemMetadata
, PlaybackStateWrapper
และ MediaSource
เนื่องจากแนวทางนี้ทำให้โค้ดซ้ำกัน (แอปไคลเอ็นต์แต่ละแอปจะเพิ่ม Observer ใน LiveData แต่ละรายการ และ View ที่คล้ายกันจำนวนมากจะได้รับข้อมูลที่อัปเดต) เราจึงสร้าง 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 ระบบจะซ่อนมุมมอง คลาสย่อยของ 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 หรือคลาสย่อย
คลาส 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 ที่บันทึกสถานะซึ่งเชื่อมโยงกับ Fregment หรือ Activity ที่ควรใช้ในการสร้างเนื้อหาของการ์ดสื่อขึ้นมาใหม่หากมีการเปลี่ยนแปลงการกําหนดค่า (เช่น การเปลี่ยนจากธีมสว่างเป็นธีมดาร์กเมื่อผู้ใช้ขับรถผ่านอุโมงค์) PlaybackCardViewModel
เริ่มต้นจะจัดการการเก็บอินสแตนซ์ของ MediaModel
ไว้สําหรับการเล่น ซึ่งสามารถดึงข้อมูล PlaybackViewModel
และ MediaItemsRepository
ได้จากอินสแตนซ์ดังกล่าว ใช้ PlaybackCardViewModel
เพื่อติดตามสถานะของเ queues, history และ overflow menu ผ่าน 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
โดยตรงได้ แต่เราได้เพิ่มคลาส PlaybackQueueController
ลงในไลบรารี car-media-common
เพื่อปรับปรุงกระบวนการนี้ให้มีประสิทธิภาพยิ่งขึ้น แอปไคลเอ็นต์จะระบุเลย์เอาต์สำหรับแต่ละรายการใน CarUiRecyclerView
รวมถึงเลย์เอาต์ส่วนหัวที่ไม่บังคับ แอปไคลเอ็นต์ยังเลือกจํากัดจํานวนรายการที่แสดงในคิวระหว่างสถานะการขับรถได้ด้วยข้อจํากัด UXR ที่กําหนดเอง
ตัวสร้างและตัวตั้งค่า PlaybackQueueController
จะแสดงในตัวอย่างต่อไปนี้ ทรัพยากรเลย์เอาต์ queueResource
และ headerResource
สามารถส่งเป็น Resources.ID_NULL
ได้ในกรณีที่คอนเทนเนอร์มี CarUiRecyclerView
ที่มี id queue_list
อยู่แล้ว และในกรณีที่คิวไม่มีส่วนหัว
/**
* 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
ในคอนสตรคเตอร์ PlaybackCardController
โดยใช้ mDataModel
และ mItemsRepository
สำหรับอินสแตนซ์ PlaybackViewModel
และ MediaItemsRepository
ตามลำดับ
แสดงประวัติของ MediaSource ที่เล่นก่อนหน้านี้
ในส่วนนี้ คุณจะได้ดูวิธีแสดงและแสดงประวัติของแหล่งที่มาของสื่อที่เล่นก่อนหน้านี้
รับรายการประวัติด้วย PlaybackCardViewModel API
PlaybackCardViewModel
มี LiveData API ชื่อ getHistoryList()
เพื่อดึงข้อมูลรายการประวัติสื่อ ซึ่งจะแสดงผล LiveData ที่มีรายการ MediaSource ที่เล่นไปก่อนหน้านี้ ข้อมูลนี้สามารถใช้เพื่อป้อนข้อมูลออบเจ็กต์ CarUiRecyclerView
เราได้เพิ่มคลาสชื่อ PlaybackHistoryController
ลงในไลบรารี car-media-common
เพื่อปรับปรุงกระบวนการให้มีประสิทธิภาพมากขึ้นแล้ว ซึ่งคล้ายกับ PlaybackQueueController
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 ประวัติของ Surface ที่มี PlaybackHistoryController
ใช้ PlaybackHistoryController
ใหม่เพื่อช่วยป้อนข้อมูลประวัติลงใน CarUiRecyclerView
ตัวสร้างและฟังก์ชันหลักของคลาสนี้มีดังนี้ คอนเทนเนอร์ที่ส่งมาจากแอปไคลเอ็นต์ควรมี 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
ในคอนสตรัคเตอร์ของ PlaybackCardController