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. Implementasi contoh Kartu Media.
Bagaimana kartu media diterapkan di AAOS?
ViewGroup yang menampilkan informasi media mengamati update LiveData dari
model data library car-media-common
, PlaybackViewModel
, untuk mengisi
ViewGroup. Setiap update LiveData sesuai dengan subkumpulan informasi media
yang telah berubah, seperti MediaItemMetadata
, PlaybackStateWrapper
, dan
MediaSource
.
Karena pendekatan ini menghasilkan kode berulang (setiap aplikasi klien menambahkan Observer pada
setiap bagian LiveData dan banyak View serupa diberi data yang diperbarui), kita
membuat PlaybackCardController
.
PlaybackCardController
PlaybackCardController
telah ditambahkan ke library car-media-common
untuk
membantu membuat kartu media. Ini adalah class publik yang dibuat dengan
instance ViewGroup (mView
), PlaybackViewModel (mDataModel
), PlaybackCardViewModel
(mViewModel
), dan 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 gambar
album ImageBinder
mAlbumArtBinder
. Jika metadata null, Tampilan akan
disembunyikan. Subclass 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);
}
}
Memperluas 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 Pengontrol harus dibuat instance-nya dari Fragment atau Aktivitas agar memiliki LifecycleOwner untuk observer LiveData.
mMediaCardController = (MediaCardController) new MediaCardController.Builder()
.setModels(mViewModel.getPlaybackViewModel(),
mViewModel,
mViewModel.getMediaItemsRepository())
.setViewGroup((ViewGroup) view)
.build();
mViewModel
adalah instance PlaybackCardViewModel
(atau subclass).
PlaybackCardViewModel untuk Menyimpan Status
PlaybackCardViewModel
adalah ViewModel penyimpanan status yang terikat dengan Fragment atau
Aktivitas yang harus digunakan untuk merekonstruksi konten kartu media jika
terjadi perubahan konfigurasi (seperti beralih dari tema terang ke gelap saat
pengguna mengemudi melalui terowongan). PlaybackCardViewModel
default menangani
penyimpanan instance MediaModel
untuk pemutaran, tempat
PlaybackViewModel
dan MediaItemsRepository
dapat diambil. Gunakan
PlaybackCardViewModel
untuk melacak status menu antrean, histori, dan tambahan
melalui pengambil dan penyetel 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 diperpanjang jika status tambahan perlu dilacak.
Menampilkan antrean di kartu media
PlaybackViewModel
menyediakan LiveData API untuk mendeteksi apakah MediaSource
mendukung antrean dan 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 di CarUiRecyclerView
ditetapkan oleh aplikasi klien serta
tata letak Header opsional. Aplikasi klien juga dapat memilih untuk membatasi jumlah
item yang ditampilkan dalam antrean selama status drive dengan batasan UXR kustom.
Konstruktor dan penyetel 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 yang terakhir, 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 MediaSources yang diputar sebelumnya
Di bagian ini, Anda akan mempelajari cara menampilkan dan menampilkan histori sumber media yang diputar sebelumnya.
Mendapatkan daftar histori dengan PlaybackCardViewModel API
PlaybackCardViewModel
menyediakan LiveData API 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 platform 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 View 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 dalam kartu media yang dibuat dengan
PlaybackCardController
(atau subclass), PlaybackHistoryController
dapat
dibuat dalam konstruktor PlaybackCardController
.