diff options
author | Joshua Brown <keyboardr@google.com> | 2018-04-27 13:20:02 -0700 |
---|---|---|
committer | Joshua Brown <keyboardr@google.com> | 2018-05-02 11:40:05 -0700 |
commit | 30ff1007ab6729566d053649d3e973ac9fd33627 (patch) | |
tree | 523c2a5effee661a20c9b1b6be3b0b6a8876b86d /src/com/android | |
parent | 3b78b872b97b59880ad46c96114bc349570fc333 (diff) | |
download | Media-30ff1007ab6729566d053649d3e973ac9fd33627.tar.gz |
DO NOT MERGE Show/hide playback queue
Flattens layouts so that they can be easily transitioned with ConstraintSets. Extracts metadata logic into MetadataController.
Bug: 78784683
Test: Manual
Change-Id: I55f2de884e278fc38fe23a64a84a48d156959747
Diffstat (limited to 'src/com/android')
-rw-r--r-- | src/com/android/car/media/MetadataController.java | 153 | ||||
-rw-r--r-- | src/com/android/car/media/PlaybackFragment.java | 140 | ||||
-rw-r--r-- | src/com/android/car/media/widgets/MetadataView.java | 155 |
3 files changed, 246 insertions, 202 deletions
diff --git a/src/com/android/car/media/MetadataController.java b/src/com/android/car/media/MetadataController.java new file mode 100644 index 0000000..f5c382a --- /dev/null +++ b/src/com/android/car/media/MetadataController.java @@ -0,0 +1,153 @@ +package com.android.car.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.view.View; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.android.car.media.common.MediaItemMetadata; +import com.android.car.media.common.PlaybackModel; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Objects; + +/** + * Common controller for displaying current track's metadata. + */ +public class MetadataController { + + private static final DateFormat TIME_FORMAT = new SimpleDateFormat("m:ss", Locale.US); + + @NonNull + private final TextView mTitle; + @NonNull + private final TextView mSubtitle; + @Nullable + private final TextView mTime; + @NonNull + private final SeekBar mSeekBar; + @Nullable + private final ImageView mAlbumArt; + + @Nullable + private PlaybackModel mModel; + @Nullable + private MediaItemMetadata mCurrentMetadata; + + private final PlaybackModel.PlaybackObserver mPlaybackObserver = + new PlaybackModel.PlaybackObserver() { + @Override + public void onPlaybackStateChanged() { + updateState(); + } + + @Override + public void onSourceChanged() { + updateState(); + updateMetadata(); + } + + @Override + public void onMetadataChanged() { + updateMetadata(); + } + }; + + /** + * Create a new MetadataController that operates on the provided Views + * @param title Displays the track's title. Must not be {@code null}. + * @param subtitle Displays the track's artist. Must not be {@code null}. + * @param time Displays the track's progress as text. May be {@code null}. + * @param seekBar Displays the track's progress visually. Must not be {@code null}. + * @param albumArt Displays the track's album art. May be {@code null}. + */ + public MetadataController(@NonNull TextView title, @NonNull TextView subtitle, + @Nullable TextView time, @NonNull SeekBar seekBar, @Nullable ImageView albumArt) { + mTitle = title; + mSubtitle = subtitle; + mTime = time; + mSeekBar = seekBar; + mAlbumArt = albumArt; + } + + /** + * Registers the {@link PlaybackModel} this widget will use to follow playback state. + * Consumers of this class must unregister the {@link PlaybackModel} by calling this method with + * null. + * + * @param model {@link PlaybackModel} to subscribe, or null to unsubscribe. + */ + public void setModel(@Nullable PlaybackModel model) { + if (mModel != null) { + mModel.unregisterObserver(mPlaybackObserver); + } + mModel = model; + if (mModel != null) { + mModel.registerObserver(mPlaybackObserver); + } + } + + private void updateState() { + updateProgress(); + + if (mModel != null && mModel.isPlaying()) { + mSeekBar.post(mSeekBarRunnable); + } else { + mSeekBar.removeCallbacks(mSeekBarRunnable); + } + } + + private void updateMetadata() { + MediaItemMetadata metadata = mModel != null ? mModel.getMetadata() : null; + if (Objects.equals(mCurrentMetadata, metadata)) { + return; + } + mCurrentMetadata = metadata; + mTitle.setText(metadata != null ? metadata.getTitle() : null); + mSubtitle.setText(metadata != null ? metadata.getSubtitle() : null); + if (mAlbumArt != null) { + MediaItemMetadata.updateImageView(mAlbumArt.getContext(), metadata, mAlbumArt, 0); + } + } + + private static final long SEEK_BAR_UPDATE_TIME_INTERVAL_MS = 1000; + + private final Runnable mSeekBarRunnable = new Runnable() { + @Override + public void run() { + if (mModel == null || !mModel.isPlaying()) { + return; + } + updateProgress(); + mSeekBar.postDelayed(this, SEEK_BAR_UPDATE_TIME_INTERVAL_MS); + + } + }; + + private void updateProgress() { + if (mModel == null) { + mTime.setVisibility(View.INVISIBLE); + mSeekBar.setVisibility(View.INVISIBLE); + return; + } + long maxProgress = mModel.getMaxProgress(); + int visibility = maxProgress > 0 ? View.VISIBLE : View.INVISIBLE; + if (mTime != null) { + String time = String.format("%s / %s", + TIME_FORMAT.format(new Date(mModel.getProgress())), + TIME_FORMAT.format(new Date(maxProgress))); + mTime.setVisibility(visibility); + mTime.setText(time); + } + mSeekBar.setVisibility(visibility); + mSeekBar.setMax((int) mModel.getMaxProgress()); + mSeekBar.setProgress((int) mModel.getProgress()); + } + + +} diff --git a/src/com/android/car/media/PlaybackFragment.java b/src/com/android/car/media/PlaybackFragment.java index 6b4994a..b133ff8 100644 --- a/src/com/android/car/media/PlaybackFragment.java +++ b/src/com/android/car/media/PlaybackFragment.java @@ -18,30 +18,33 @@ package com.android.car.media; import android.content.Context; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.constraint.ConstraintLayout; +import android.support.constraint.ConstraintSet; import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; +import android.transition.Transition; +import android.transition.TransitionInflater; +import android.transition.TransitionManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.car.widget.ListItem; +import androidx.car.widget.ListItemAdapter; +import androidx.car.widget.ListItemProvider; +import androidx.car.widget.PagedListView; +import androidx.car.widget.TextListItem; import com.android.car.media.common.MediaItemMetadata; import com.android.car.media.common.MediaSource; import com.android.car.media.common.PlaybackControls; import com.android.car.media.common.PlaybackModel; -import com.android.car.media.widgets.MetadataView; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.List; -import java.util.Locale; -import java.util.Objects; - -import androidx.car.widget.ListItem; -import androidx.car.widget.ListItemAdapter; -import androidx.car.widget.ListItemProvider; -import androidx.car.widget.PagedListView; -import androidx.car.widget.TextListItem; /** * A {@link Fragment} that implements both the playback and the content forward browsing experience. @@ -50,34 +53,32 @@ import androidx.car.widget.TextListItem; */ public class PlaybackFragment extends Fragment { private static final String TAG = "PlaybackFragment"; - private static final DateFormat TIME_FORMAT = new SimpleDateFormat("m:ss", Locale.US); private PlaybackModel mModel; private PlaybackControls mPlaybackControls; - private ImageView mAlbumArt; - private MetadataView mMetadataView; - private PagedListView mQueueList; private QueueItemsAdapter mQueueAdapter; - private MediaItemMetadata mCurrentMetadata; - private boolean mQueueIsVisible; - private PlaybackModel.PlaybackObserver mPlaybackObserver = new PlaybackModel.PlaybackObserver() { - @Override - public void onPlaybackStateChanged() { - updateState(); - } - @Override - public void onSourceChanged() { - updateState(); - updateMetadata(); - updateAccentColor(); - } + private MetadataController mMetadataController; + private ConstraintLayout mRootView; - @Override - public void onMetadataChanged() { - updateMetadata(); - } - }; + private boolean mQueueIsVisible; + private PlaybackModel.PlaybackObserver mPlaybackObserver = + new PlaybackModel.PlaybackObserver() { + @Override + public void onPlaybackStateChanged() { + updateState(); + } + + @Override + public void onSourceChanged() { + updateAccentColor(); + updateState(); + } + + @Override + public void onMetadataChanged() { + } + }; private ListItemProvider mQueueItemsProvider = new ListItemProvider() { @Override public ListItem get(int position) { @@ -95,6 +96,7 @@ public class PlaybackFragment extends Fragment { textListItem.setOnClickListener(v -> onQueueItemClicked(item)); return textListItem; } + @Override public int size() { if (!mModel.hasQueue()) { @@ -103,6 +105,7 @@ public class PlaybackFragment extends Fragment { return mModel.getQueue().size(); } }; + private static class QueueItemsAdapter extends ListItemAdapter { QueueItemsAdapter(Context context, ListItemProvider itemProvider) { super(context, itemProvider, BackgroundStyle.SOLID); @@ -114,70 +117,91 @@ public class PlaybackFragment extends Fragment { this.notifyDataSetChanged(); } } + private PlaybackControls.Listener mPlaybackControlsListener = new PlaybackControls.Listener() { @Override public void onToggleQueue() { mQueueIsVisible = !mQueueIsVisible; mPlaybackControls.setQueueVisible(mQueueIsVisible); + setQueueVisible(mQueueIsVisible); } }; @Override - public View onCreateView(LayoutInflater inflater, final ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_playback, container, false); + mRootView = view.findViewById(R.id.playback_container); mModel = new PlaybackModel(getContext()); - mPlaybackControls = view.findViewById(R.id.playback_controls); + + initPlaybackControls(view.findViewById(R.id.playback_controls)); + initQueue(view.findViewById(R.id.queue_list)); + initMetadataController(view); + return view; + } + + private void initPlaybackControls(PlaybackControls playbackControls) { + mPlaybackControls = playbackControls; mPlaybackControls.setModel(mModel); mPlaybackControls.setListener(mPlaybackControlsListener); - ViewGroup playbackContainer = view.findViewById(R.id.playback_container); - mPlaybackControls.setAnimationViewGroup(playbackContainer); - mAlbumArt = view.findViewById(R.id.album_art); - mMetadataView = view.findViewById(R.id.metadata); - mQueueList = view.findViewById(R.id.queue_list); - RecyclerView recyclerView = mQueueList.getRecyclerView(); + mPlaybackControls.setAnimationViewGroup(mRootView); + } + + private void initQueue(PagedListView queueList) { + RecyclerView recyclerView = queueList.getRecyclerView(); recyclerView.setVerticalFadingEdgeEnabled(true); recyclerView.setFadingEdgeLength(getResources() .getDimensionPixelSize(R.dimen.car_padding_4)); mQueueAdapter = new QueueItemsAdapter(getContext(), mQueueItemsProvider); - mQueueList.setAdapter(mQueueAdapter); + queueList.setAdapter(mQueueAdapter); + } - return view; + private void initMetadataController(View view) { + ImageView albumArt = view.findViewById(R.id.album_art); + TextView title = view.findViewById(R.id.title); + TextView subtitle = view.findViewById(R.id.subtitle); + SeekBar seekbar = view.findViewById(R.id.seek_bar); + TextView time = view.findViewById(R.id.time); + mMetadataController = new MetadataController(title, subtitle, time, seekbar, albumArt); + mMetadataController.setModel(mModel); + } + + + @Override + public void onDestroyView() { + super.onDestroyView(); + mPlaybackControls.setModel(null); + mMetadataController.setModel(null); + mMetadataController = null; } @Override public void onStart() { super.onStart(); mModel.registerObserver(mPlaybackObserver); - mMetadataView.setModel(mModel); } @Override public void onStop() { super.onStop(); mModel.unregisterObserver(mPlaybackObserver); - mMetadataView.setModel(null); - mCurrentMetadata = null; } - @Override - public void onDestroyView() { - super.onDestroyView(); - mPlaybackControls.setModel(null); + public void setQueueVisible(boolean visible) { + Transition transition = TransitionInflater.from(getContext()).inflateTransition( + visible ? R.transition.queue_in : R.transition.queue_out); + TransitionManager.beginDelayedTransition(mRootView, transition); + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(mRootView.getContext(), + visible ? R.layout.fragment_playback_with_queue : R.layout.fragment_playback); + constraintSet.applyTo(mRootView); + } private void updateState() { mQueueAdapter.refresh(); } - private void updateMetadata() { - MediaItemMetadata metadata = mModel.getMetadata(); - if (Objects.equals(mCurrentMetadata, metadata)) { - return; - } - mCurrentMetadata = metadata; - MediaItemMetadata.updateImageView(getContext(), metadata, mAlbumArt, 0); - } private void updateAccentColor() { int defaultColor = getResources().getColor(android.R.color.background_dark, null); diff --git a/src/com/android/car/media/widgets/MetadataView.java b/src/com/android/car/media/widgets/MetadataView.java index 4b3fb38..6d9e72a 100644 --- a/src/com/android/car/media/widgets/MetadataView.java +++ b/src/com/android/car/media/widgets/MetadataView.java @@ -1,117 +1,43 @@ package com.android.car.media.widgets; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.annotation.IntDef; import android.annotation.Nullable; import android.content.Context; -import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.LayoutInflater; -import android.view.View; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; +import com.android.car.media.MetadataController; import com.android.car.media.R; -import com.android.car.media.common.MediaItemMetadata; import com.android.car.media.common.PlaybackModel; -import java.lang.annotation.Retention; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Objects; - /** * A view that can be used to display the metadata and playback progress of a media item. * This view can be styled using the "MetadataView" styleable attributes. */ public class MetadataView extends RelativeLayout { - private static final DateFormat TIME_FORMAT = new SimpleDateFormat("m:ss", Locale.US); - - @Nullable - private MediaItemMetadata mCurrentMetadata; - private TextView mTitle; - private TextView mTime; - private TextView mSubtitle; - private SeekBar mSeekbar; - @Nullable - private PlaybackModel mModel; - - private PlaybackModel.PlaybackObserver mPlaybackObserver = - new PlaybackModel.PlaybackObserver() { - @Override - public void onPlaybackStateChanged() { - updateState(); - } - - @Override - public void onSourceChanged() { - updateState(); - updateMetadata(); - } - - @Override - public void onMetadataChanged() { - updateMetadata(); - } - }; - - /** - * The possible styles of this widget. - */ - @IntDef({ - MetadataView.Style.COMPACT, - MetadataView.Style.NORMAL - }) - @Retention(SOURCE) - public @interface Style { - /** Compact style (small space between elements, no time progress indicator) */ - int COMPACT = 0; - /** Normal style (normal spacing and progress indicator) */ - int NORMAL = 1; - } + private final MetadataController mMetadataController; public MetadataView(Context context) { - super(context); - init(context, null, 0, 0); + this(context, null); } public MetadataView(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0, 0); + this(context, attrs, 0); } public MetadataView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr, 0); + this(context, attrs, defStyleAttr, 0); } public MetadataView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - init(context, attrs, defStyleAttr, defStyleRes); - } - - private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - TypedArray ta = context.obtainStyledAttributes( - attrs, R.styleable.MetadataView, defStyleAttr, defStyleRes); - @Style int style = ta.getInteger(R.styleable.MetadataView_style, Style.NORMAL); - ta.recycle(); - - int layoutId = style == Style.COMPACT - ? R.layout.metadata_compact - : R.layout.metadata_normal; - - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(layoutId, this, true); - - mTitle = findViewById(R.id.title); - mSubtitle = findViewById(R.id.subtitle); - mSeekbar = findViewById(R.id.seek_bar); - mTime = findViewById(R.id.time); + LayoutInflater.from(context).inflate(R.layout.metadata_compact, this, true); + TextView title = findViewById(R.id.title); + TextView subtitle = findViewById(R.id.subtitle); + SeekBar seekBar = findViewById(R.id.seek_bar); + mMetadataController = new MetadataController(title, subtitle, null, seekBar, null); } /** @@ -122,66 +48,7 @@ public class MetadataView extends RelativeLayout { * @param model {@link PlaybackModel} to subscribe, or null to unsubscribe. */ public void setModel(@Nullable PlaybackModel model) { - if (mModel != null) { - mModel.unregisterObserver(mPlaybackObserver); - } - mModel = model; - if (mModel != null) { - mModel.registerObserver(mPlaybackObserver); - } - } - - private void updateState() { - updateProgress(); - - if (mModel != null && mModel.isPlaying()) { - mSeekbar.post(mSeekBarRunnable); - } else { - mSeekbar.removeCallbacks(mSeekBarRunnable); - } + mMetadataController.setModel(model); } - private void updateMetadata() { - MediaItemMetadata metadata = mModel != null ? mModel.getMetadata() : null; - if (Objects.equals(mCurrentMetadata, metadata)) { - return; - } - mCurrentMetadata = metadata; - mTitle.setText(metadata != null ? metadata.getTitle() : null); - mSubtitle.setText(metadata != null ? metadata.getSubtitle() : null); - } - - private static final long SEEK_BAR_UPDATE_TIME_INTERVAL_MS = 1000; - - private final Runnable mSeekBarRunnable = new Runnable() { - @Override - public void run() { - if (mModel == null || !mModel.isPlaying()) { - return; - } - updateProgress(); - mSeekbar.postDelayed(this, SEEK_BAR_UPDATE_TIME_INTERVAL_MS); - - } - }; - - private void updateProgress() { - if (mModel == null) { - mTime.setVisibility(View.INVISIBLE); - mSeekbar.setVisibility(View.INVISIBLE); - return; - } - long maxProgress = mModel.getMaxProgress(); - int visibility = maxProgress > 0 ? View.VISIBLE : View.INVISIBLE; - if (mTime != null) { - String time = String.format("%s / %s", - TIME_FORMAT.format(new Date(mModel.getProgress())), - TIME_FORMAT.format(new Date(maxProgress))); - mTime.setVisibility(visibility); - mTime.setText(time); - } - mSeekbar.setVisibility(visibility); - mSeekbar.setMax((int) mModel.getMaxProgress()); - mSeekbar.setProgress((int) mModel.getProgress()); - } } |