Kartu media adalah ViewGroup mandiri yang menampilkan metadata media seperti judul, gambar album, dan lainnya, serta menampilkan kontrol pemutaran seperti Putar dan Jeda, Lewati,dan bahkan tindakan kustom yang disediakan oleh aplikasi media pihak ketiga. Kartu media juga dapat menampilkan antrean item media, seperti playlist.
Gambar 1. Contoh penerapan Kartu Media.
Bagaimana kartu media diimplementasikan di AAOS?
ViewGroup yang menampilkan informasi media mengamati update LiveData dari model data pustaka car-media-common
, PlaybackViewModel
, untuk mengisi ViewGroup. Setiap update LiveData sesuai dengan subset informasi media yang telah berubah, seperti MediaItemMetadata
, PlaybackStateWrapper
, dan MediaSource
.
Karena pendekatan ini menyebabkan kode berulang (setiap aplikasi klien menambahkan Observer pada setiap bagian LiveData dan banyak View serupa diberi data yang diperbarui), kami membuat PlaybackCardController
.
PlaybackCardController
PlaybackCardController
telah ditambahkan ke pustaka car-media-common
untuk membantu membuat kartu media. Ini adalah class publik yang dibuat dengan
ViewGroup (mView
), PlaybackViewModel (mDataModel
), PlaybackCardViewModel
(mViewModel
), dan instance MediaItemsRepository
(mItemsRepository
).
Dalam fungsi setupController
, ViewGroup diuraikan untuk tampilan tertentu berdasarkan
ID, dengan mView.findViewById(R.id.xxx)
dan ditetapkan ke objek View yang dilindungi.
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);
// ...
}
Setiap update LiveData dari PlaybackViewModel
diamati dalam metode yang dilindungi dan melakukan interaksi dengan View yang relevan dengan data yang diterima. Misalnya, observer di MediaItemMetadata
menetapkan judul di
mTitle
TextView
dan meneruskan MediaItemMetadata.ArtworkRef
ke sampul
album ImageBinder
mAlbumArtBinder
. Jika metadata bernilai null, Tampilan akan disembunyikan. Subkelas Pengontrol dapat mengganti logika ini jika diperlukan.
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);
}
}
Perluas PlaybackCardController
Aplikasi klien yang ingin membuat kartu media harus memperluas
PlaybackCardController
jika memiliki kemampuan tambahan yang ingin
ditangani di setiap update LiveData. Klien yang ada di AAOS mengikuti pola ini.
Pertama, subclass PlaybackCardController
harus dibuat, seperti
MediaCardController
. Selanjutnya, MediaCardController
harus menambahkan class Builder dalam statis
yang memperluas class 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
// ...
}
}
Membuat instance PlaybackCardController atau subclass
Class Controller harus di-instance dari Fragment atau Activity agar memiliki LifecycleOwner untuk pengamat LiveData.
mMediaCardController = (MediaCardController) new MediaCardController.Builder()
.setModels(mViewModel.getPlaybackViewModel(),
mViewModel,
mViewModel.getMediaItemsRepository())
.setViewGroup((ViewGroup) view)
.build();
mViewModel
adalah instance dari PlaybackCardViewModel
(atau subclass).
PlaybackCardViewModel untuk Menyimpan Status
PlaybackCardViewModel
adalah ViewModel penyimpanan status yang terikat ke Fragment atau
Aktivitas yang harus digunakan untuk merekonstruksi konten kartu media jika terjadi
perubahan konfigurasi (seperti peralihan dari tema terang ke tema gelap saat
pengguna melewati terowongan). PlaybackCardViewModel
default menangani penyimpanan instance MediaModel
untuk pemutaran, yang dapat digunakan untuk mengambil PlaybackViewModel
dan MediaItemsRepository
. Gunakan
PlaybackCardViewModel
untuk melacak status antrean, histori, dan menu
tambahan melalui getter dan setter yang disediakan.
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;
}
}
Class ini dapat diperluas jika ada status tambahan yang perlu dilacak.
Menampilkan antrean di kartu media
PlaybackViewModel
menyediakan API LiveData untuk mendeteksi apakah MediaSource mendukung antrean dan untuk mengambil daftar objek MediaItemMetadata
dalam antrean. Meskipun API ini dapat digunakan secara langsung untuk mengisi objek RecyclerView
dengan informasi antrean, class PlaybackQueueController
telah ditambahkan ke library car-media-common
untuk menyederhanakan proses ini. Tata letak
untuk setiap item dalam CarUiRecyclerView
ditentukan oleh aplikasi klien serta
tata letak Header opsional. Aplikasi klien juga dapat memilih untuk membatasi jumlah
item yang ditampilkan dalam antrean selama status mengemudi dengan batasan UXR kustom.
Konstruktor dan setter PlaybackQueueController
ditampilkan dalam contoh berikut. Resource tata letak queueResource
dan headerResource
dapat diteruskan sebagai Resources.ID_NULL
jika, dalam kasus pertama, penampung sudah berisi CarUiRecyclerView
dengan id queue_list
dan, dalam kasus kedua, antrean tidak memiliki 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;
}
Tata letak untuk setiap item antrean harus berisi ID untuk Tampilan yang ingin ditampilkan yang sesuai dengan yang digunakan di class dalam 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);
// ...
}
Untuk menampilkan antrean di kartu media yang dibuat dengan PlaybackCardController
(atau subclass), PlaybackQueueController
dapat dibuat di
konstruktor PlaybackCardController
menggunakan mDataModel
dan mItemsRepository
untuk instance PlaybackViewModel
dan MediaItemsRepository
.
Menampilkan histori MediaSource yang diputar sebelumnya
Di bagian ini, Anda akan mempelajari cara menampilkan dan memunculkan histori sumber media yang sebelumnya diputar.
Mendapatkan daftar histori dengan PlaybackCardViewModel API
PlaybackCardViewModel
menyediakan API LiveData yang disebut getHistoryList()
untuk mengambil daftar histori media. Metode ini menampilkan LiveData yang berisi daftar
MediaSource yang telah diputar sebelumnya. Data ini dapat digunakan untuk mengisi
objek CarUiRecyclerView
. Serupa dengan PlaybackQueueController
, class
bernama PlaybackHistoryController
telah ditambahkan ke library car-media-common
untuk menyederhanakan proses.
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 histori permukaan dengan PlaybackHistoryController
Gunakan PlaybackHistoryController
baru untuk membantu mengisi data histori ke CarUiRecyclerView
. Konstruktor dan fungsi utama class ini adalah sebagai berikut. Penampung yang diteruskan dari aplikasi klien harus berisi
CarUiRecyclerView
dengan ID history_list
. CarUiRecyclerView
menampilkan item daftar dan header opsional. Tata letak untuk item daftar dan header dapat diteruskan dari aplikasi klien. Jika Resources.ID_NULL
ditetapkan sebagai headerResource, header tidak akan ditampilkan. Setelah
PlaybackCardViewModel
diteruskan ke pengontrol, pengontrol akan memantau
LiveData<List<MediaSource>>
yang diambil dari
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() {
}
}
Tata letak untuk setiap item harus berisi ID untuk Tampilan yang ingin ditampilkan yang sesuai dengan yang digunakan di class dalam 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);
// ...
}
Untuk menampilkan daftar histori di kartu media yang dibuat dengan
PlaybackCardController
(atau subclass), PlaybackHistoryController
dapat
dibuat di konstruktor PlaybackCardController
.