ใช้การ์ดสื่อใน AAOS

การ์ดสื่อคือ 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