Implement SlidingPlaybackActivity

This commit is contained in:
Adrian Ulrich 2016-04-23 08:47:40 +02:00
parent ffb2c1332a
commit 9e42e86296
5 changed files with 291 additions and 183 deletions

View File

@ -66,6 +66,39 @@ THE SOFTWARE.
android:orientation="horizontal">
<include layout="@layout/bottombar_controls" android:id="@+id/bottombar_controls" />
</LinearLayout>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_gravity="top"
android:background="?overlay_background_color"
android:elevation="2dp"
android:orientation="horizontal">
<TextView
android:id="@+id/elapsed"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:paddingLeft="5dip" />
<SeekBar
android:id="@+id/seek_bar"
android:layout_height="wrap_content"
android:layout_width="0px"
android:layout_gravity="center"
android:paddingTop="5dip"
android:paddingBottom="5dip"
android:paddingLeft="18dip"
android:paddingRight="18dip"
android:layout_weight="1" />
<TextView
android:id="@+id/duration"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:paddingRight="5dip" />
</LinearLayout>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"

View File

@ -32,7 +32,6 @@ import android.media.MediaMetadataRetriever;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.text.format.DateUtils;
import android.util.Log;
import android.content.ContentResolver;
import android.view.Gravity;
@ -54,9 +53,8 @@ import android.content.DialogInterface;
/**
* The primary playback screen with playback controls and large cover display.
*/
public class FullPlaybackActivity extends PlaybackActivity
implements SeekBar.OnSeekBarChangeListener
, View.OnLongClickListener
public class FullPlaybackActivity extends SlidingPlaybackActivity
implements View.OnLongClickListener
{
public static final int DISPLAY_INFO_OVERLAP = 0;
public static final int DISPLAY_INFO_BELOW = 1;
@ -65,10 +63,7 @@ public class FullPlaybackActivity extends PlaybackActivity
private TextView mOverlayText;
private View mControlsTop;
private SeekBar mSeekBar;
private TableLayout mInfoTable;
private TextView mElapsedView;
private TextView mDurationView;
private TextView mQueuePosView;
private TextView mTitle;
@ -83,12 +78,6 @@ public class FullPlaybackActivity extends PlaybackActivity
* True if the extra info is visible.
*/
private boolean mExtraInfoVisible;
/**
* Current song duration in milliseconds.
*/
private long mDuration;
private boolean mSeekBarTracking;
private boolean mPaused;
/**
* The current display mode, which determines layout and cover render style.
@ -98,10 +87,6 @@ public class FullPlaybackActivity extends PlaybackActivity
private Action mCoverPressAction;
private Action mCoverLongPressAction;
/**
* Cached StringBuilder for formatting track position.
*/
private final StringBuilder mTimeBuilder = new StringBuilder();
/**
* The currently playing song.
*/
@ -172,11 +157,6 @@ public class FullPlaybackActivity extends PlaybackActivity
mArtist = (TextView)findViewById(R.id.artist);
mControlsTop = findViewById(R.id.controls_top);
mElapsedView = (TextView)findViewById(R.id.elapsed);
mDurationView = (TextView)findViewById(R.id.duration);
mSeekBar = (SeekBar)findViewById(R.id.seek_bar);
mSeekBar.setMax(1000);
mSeekBar.setOnSeekBarChangeListener(this);
mQueuePosView = (TextView)findViewById(R.id.queue_pos);
mGenreView = (TextView)findViewById(R.id.genre);
@ -190,7 +170,6 @@ public class FullPlaybackActivity extends PlaybackActivity
setControlsVisible(settings.getBoolean(PrefKeys.VISIBLE_CONTROLS, PrefDefaults.VISIBLE_CONTROLS));
setExtraInfoVisible(settings.getBoolean(PrefKeys.VISIBLE_EXTRA_INFO, PrefDefaults.VISIBLE_EXTRA_INFO));
setDuration(0);
}
@Override
@ -208,21 +187,6 @@ public class FullPlaybackActivity extends PlaybackActivity
mCoverLongPressAction = Action.getAction(settings, PrefKeys.COVER_LONGPRESS_ACTION, PrefDefaults.COVER_LONGPRESS_ACTION);
}
@Override
public void onResume()
{
super.onResume();
mPaused = false;
updateElapsedTime();
}
@Override
public void onPause()
{
super.onPause();
mPaused = true;
}
/**
* Hide the message overlay, if it exists.
*/
@ -276,18 +240,12 @@ public class FullPlaybackActivity extends PlaybackActivity
}
}
if ((state & PlaybackService.FLAG_PLAYING) != 0)
updateElapsedTime();
if (mQueuePosView != null)
updateQueuePosition();
}
@Override
protected void onSongChange(Song song)
{
setDuration(song == null ? 0 : song.duration);
protected void onSongChange(Song song) {
if (mTitle != null) {
if (song == null) {
mTitle.setText(null);
@ -302,7 +260,6 @@ public class FullPlaybackActivity extends PlaybackActivity
}
mCurrentSong = song;
updateElapsedTime();
mHandler.sendEmptyMessage(MSG_LOAD_FAVOURITE_INFO);
@ -338,25 +295,14 @@ public class FullPlaybackActivity extends PlaybackActivity
mUiHandler.sendEmptyMessage(MSG_UPDATE_POSITION);
}
/**
* Update the current song duration fields.
*
* @param duration The new duration, in milliseconds.
*/
private void setDuration(long duration)
{
mDuration = duration;
mDurationView.setText(DateUtils.formatElapsedTime(mTimeBuilder, duration / 1000));
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
super.onCreateOptionsMenu(menu);
menu.add(0, MENU_DELETE, 0, R.string.delete);
menu.add(0, MENU_ENQUEUE_ALBUM, 0, R.string.enqueue_current_album).setIcon(R.drawable.ic_menu_add);
menu.add(0, MENU_ENQUEUE_ARTIST, 0, R.string.enqueue_current_artist).setIcon(R.drawable.ic_menu_add);
menu.add(0, MENU_ENQUEUE_GENRE, 0, R.string.enqueue_current_genre).setIcon(R.drawable.ic_menu_add);
menu.add(0, MENU_DELETE, 30, R.string.delete);
menu.add(0, MENU_ENQUEUE_ALBUM, 30, R.string.enqueue_current_album).setIcon(R.drawable.ic_menu_add);
menu.add(0, MENU_ENQUEUE_ARTIST, 30, R.string.enqueue_current_artist).setIcon(R.drawable.ic_menu_add);
menu.add(0, MENU_ENQUEUE_GENRE, 30, R.string.enqueue_current_genre).setIcon(R.drawable.ic_menu_add);
mFavorites = menu.add(0, MENU_SONG_FAVORITE, 0, R.string.add_to_favorites).setIcon(R.drawable.btn_rating_star_off_mtrl_alpha).setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
// ensure that mFavorites is updated
@ -471,28 +417,6 @@ public class FullPlaybackActivity extends PlaybackActivity
return super.onKeyUp(keyCode, event);
}
/**
* Update seek bar progress and schedule another update in one second
*/
private void updateElapsedTime()
{
long position = PlaybackService.hasInstance() ? PlaybackService.get(this).getPosition() : 0;
if (!mSeekBarTracking) {
long duration = mDuration;
mSeekBar.setProgress(duration == 0 ? 0 : (int)(1000 * position / duration));
}
mElapsedView.setText(DateUtils.formatElapsedTime(mTimeBuilder, position / 1000));
if (!mPaused && mControlsVisible && (mState & PlaybackService.FLAG_PLAYING) != 0) {
// Try to update right after the duration increases by one second
long next = 1050 - position % 1000;
mUiHandler.removeMessages(MSG_UPDATE_PROGRESS);
mUiHandler.sendEmptyMessageDelayed(MSG_UPDATE_PROGRESS, next);
}
}
/**
* Set the visibility of the controls views.
*
@ -507,7 +431,6 @@ public class FullPlaybackActivity extends PlaybackActivity
if (visible) {
mPlayPauseButton.requestFocus();
updateElapsedTime();
}
}
@ -618,39 +541,30 @@ public class FullPlaybackActivity extends PlaybackActivity
return mime;
}
/**
* Update the seekbar progress with the current song progress. This must be
* called on the UI Handler.
*/
private static final int MSG_UPDATE_PROGRESS = 10;
/**
* Save the hidden_controls preference to storage.
*/
private static final int MSG_SAVE_CONTROLS = 14;
private static final int MSG_SAVE_CONTROLS = 10;
/**
* Call {@link #loadExtraInfo()}.
*/
private static final int MSG_LOAD_EXTRA_INFO = 15;
private static final int MSG_LOAD_EXTRA_INFO = 11;
/**
* Pass obj to mExtraInfo.setText()
*/
private static final int MSG_COMMIT_INFO = 16;
private static final int MSG_COMMIT_INFO = 12;
/**
* Calls {@link #updateQueuePosition()}.
*/
private static final int MSG_UPDATE_POSITION = 17;
/**
* Calls {@link PlaybackService#seekToProgress(int)}.
*/
private static final int MSG_SEEK_TO_PROGRESS = 18;
private static final int MSG_UPDATE_POSITION = 13;
/**
* Check if passed song is a favorite
*/
private static final int MSG_LOAD_FAVOURITE_INFO = 19;
private static final int MSG_LOAD_FAVOURITE_INFO = 14;
/**
* Updates the favorites state
*/
private static final int MSG_COMMIT_FAVOURITE_INFO = 20;
private static final int MSG_COMMIT_FAVOURITE_INFO = 15;
@Override
public boolean handleMessage(Message message)
@ -663,9 +577,6 @@ public class FullPlaybackActivity extends PlaybackActivity
editor.apply();
break;
}
case MSG_UPDATE_PROGRESS:
updateElapsedTime();
break;
case MSG_LOAD_EXTRA_INFO:
loadExtraInfo();
break;
@ -681,10 +592,6 @@ public class FullPlaybackActivity extends PlaybackActivity
case MSG_UPDATE_POSITION:
updateQueuePosition();
break;
case MSG_SEEK_TO_PROGRESS:
PlaybackService.get(this).seekToProgress(message.arg1);
updateElapsedTime();
break;
case MSG_LOAD_FAVOURITE_INFO:
if (mCurrentSong != null) {
boolean found = Playlist.isInPlaylist(getContentResolver(), Playlist.getFavoritesId(this, false), mCurrentSong);
@ -705,28 +612,6 @@ public class FullPlaybackActivity extends PlaybackActivity
return true;
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
{
if (fromUser) {
mElapsedView.setText(DateUtils.formatElapsedTime(mTimeBuilder, progress * mDuration / 1000000));
mUiHandler.removeMessages(MSG_SEEK_TO_PROGRESS);
mUiHandler.sendMessageDelayed(mUiHandler.obtainMessage(MSG_SEEK_TO_PROGRESS, progress, 0), 150);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar)
{
mSeekBarTracking = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar)
{
mSeekBarTracking = false;
}
@Override
protected void performAction(Action action)
{

View File

@ -70,7 +70,7 @@ import junit.framework.Assert;
* The library activity where songs to play can be selected from the library.
*/
public class LibraryActivity
extends PlaybackActivity
extends SlidingPlaybackActivity
implements DialogInterface.OnClickListener
, DialogInterface.OnDismissListener
, SearchView.OnQueryTextListener
@ -800,12 +800,10 @@ public class LibraryActivity
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// called before super to have it on top
menu.add(0, MENU_PLAYBACK, 0, R.string.playback_view);
super.onCreateOptionsMenu(menu);
menu.add(0, MENU_PLAYBACK, 0, R.string.playback_view);
menu.add(0, MENU_SEARCH, 0, R.string.search).setIcon(R.drawable.ic_menu_search).setVisible(false);
menu.add(0, MENU_SORT, 0, R.string.sort_by).setIcon(R.drawable.ic_menu_sort_alphabetically);
menu.add(0, MENU_SORT, 30, R.string.sort_by).setIcon(R.drawable.ic_menu_sort_alphabetically);
return true;
}

View File

@ -57,12 +57,10 @@ public abstract class PlaybackActivity extends Activity
implements TimelineCallback,
Handler.Callback,
View.OnClickListener,
CoverView.Callback,
SlidingView.Callback
CoverView.Callback
{
private Action mUpAction;
private Action mDownAction;
private Menu mMenu;
/**
* A Handler running on the UI thread, in contrast with mHandler which runs
@ -82,7 +80,6 @@ public abstract class PlaybackActivity extends Activity
protected ImageButton mPlayPauseButton;
protected ImageButton mShuffleButton;
protected ImageButton mEndButton;
protected SlidingView mSlidingView;
protected int mState;
private long mLastStateEvent;
@ -318,10 +315,6 @@ public abstract class PlaybackActivity extends Activity
mEndButton = (ImageButton)findViewById(R.id.end_action);
mEndButton.setOnClickListener(this);
registerForContextMenu(mEndButton);
mSlidingView = (SlidingView)findViewById(R.id.sliding_view);
if (mSlidingView != null)
mSlidingView.setCallback(this);
}
/**
@ -385,15 +378,7 @@ public abstract class PlaybackActivity extends Activity
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
mMenu = menu;
menu.add(0, MENU_PREFS, 0, R.string.settings).setIcon(R.drawable.ic_menu_preferences);
menu.add(0, MENU_SHOW_QUEUE, 0, R.string.show_queue);
menu.add(0, MENU_HIDE_QUEUE, 0, R.string.hide_queue);
menu.add(0, MENU_CLEAR_QUEUE, 0, R.string.dequeue_rest);
menu.add(0, MENU_EMPTY_QUEUE, 0, R.string.empty_the_queue);
menu.add(0, MENU_SAVE_QUEUE_AS_PLAYLIST, 0, R.string.save_as_playlist);
onSlideFullyExpanded(false);
menu.add(0, MENU_PREFS, 10, R.string.settings).setIcon(R.drawable.ic_menu_preferences);
return true;
}
@ -407,12 +392,6 @@ public abstract class PlaybackActivity extends Activity
case MENU_CLEAR_QUEUE:
PlaybackService.get(this).clearQueue();
break;
case MENU_SHOW_QUEUE:
mSlidingView.expandSlide();
break;
case MENU_HIDE_QUEUE:
mSlidingView.hideSlide();
break;
case MENU_EMPTY_QUEUE:
PlaybackService.get(this).emptyQueue();
break;
@ -428,33 +407,6 @@ public abstract class PlaybackActivity extends Activity
}
/**
* Called by SlidingView to signal a visibility change.
* Toggles the visibility of menu items
*
* @param expanded true if slide fully expanded
*/
@Override
public void onSlideFullyExpanded(boolean expanded) {
if (mMenu == null)
return; // not initialized yet
final int[] slide_visible = {MENU_HIDE_QUEUE, MENU_CLEAR_QUEUE, MENU_EMPTY_QUEUE, MENU_SAVE_QUEUE_AS_PLAYLIST};
final int[] slide_hidden = {MENU_SHOW_QUEUE, MENU_SORT, MENU_DELETE, MENU_ENQUEUE_ALBUM, MENU_ENQUEUE_ARTIST, MENU_ENQUEUE_GENRE};
for (int id : slide_visible) {
MenuItem item = mMenu.findItem(id);
if (item != null)
item.setVisible(expanded);
}
for (int id : slide_hidden) {
MenuItem item = mMenu.findItem(id);
if (item != null)
item.setVisible(!expanded);
}
}
/**
* Call addToPlaylist with the results from a NewPlaylistDialog stored in
* obj.

View File

@ -0,0 +1,240 @@
/*
* Copyright (C) 2016 Adrian Ulrich <adrian@blinkenlights.ch>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ch.blinkenlights.android.vanilla;
import android.text.format.DateUtils;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.SeekBar;
import android.widget.TextView;
public class SlidingPlaybackActivity extends PlaybackActivity
implements SlidingView.Callback,
SeekBar.OnSeekBarChangeListener
{
/**
* Reference to the inflated menu
*/
private Menu mMenu;
/**
* SeekBar widget
*/
private SeekBar mSeekBar;
/**
* TextView indicating the elapsed playback time
*/
private TextView mElapsedView;
/**
* TextView indicating the total duration of the song
*/
private TextView mDurationView;
/**
* Current song duration in milliseconds.
*/
private long mDuration;
/**
* True if user tracks/drags the seek bar
*/
private boolean mSeekBarTracking;
/**
* True if the seek bar should not get periodic updates
*/
private boolean mPaused;
/**
* Cached StringBuilder for formatting track position.
*/
private final StringBuilder mTimeBuilder = new StringBuilder();
/**
* Instance of the sliding view
*/
protected SlidingView mSlidingView;
@Override
protected void bindControlButtons() {
super.bindControlButtons();
mSlidingView = (SlidingView)findViewById(R.id.sliding_view);
mSlidingView.setCallback(this);
mElapsedView = (TextView)findViewById(R.id.elapsed);
mDurationView = (TextView)findViewById(R.id.duration);
mSeekBar = (SeekBar)findViewById(R.id.seek_bar);
mSeekBar.setMax(1000);
mSeekBar.setOnSeekBarChangeListener(this);
setDuration(0);
}
@Override
public void onResume() {
super.onResume();
mPaused = false;
updateElapsedTime();
}
@Override
public void onPause() {
super.onPause();
mPaused = true;
}
@Override
protected void onSongChange(Song song) {
setDuration(song == null ? 0 : song.duration);
updateElapsedTime();
super.onSongChange(song);
}
@Override
protected void onStateChange(int state, int toggled) {
updateElapsedTime();
super.onStateChange(state, toggled);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
mMenu = menu;
menu.add(0, MENU_SHOW_QUEUE, 20, R.string.show_queue);
menu.add(0, MENU_HIDE_QUEUE, 20, R.string.hide_queue);
menu.add(0, MENU_CLEAR_QUEUE, 20, R.string.dequeue_rest);
menu.add(0, MENU_EMPTY_QUEUE, 20, R.string.empty_the_queue);
menu.add(0, MENU_SAVE_QUEUE_AS_PLAYLIST, 20, R.string.save_as_playlist);
onSlideFullyExpanded(false);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_SHOW_QUEUE:
mSlidingView.expandSlide();
break;
case MENU_HIDE_QUEUE:
mSlidingView.hideSlide();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
/**
* Update the seekbar progress with the current song progress. This must be
* called on the UI Handler.
*/
private static final int MSG_UPDATE_PROGRESS = 20;
/**
* Calls {@link PlaybackService#seekToProgress(int)}.
*/
private static final int MSG_SEEK_TO_PROGRESS = 21;
@Override
public boolean handleMessage(Message message)
{
switch (message.what) {
case MSG_UPDATE_PROGRESS:
updateElapsedTime();
break;
case MSG_SEEK_TO_PROGRESS:
PlaybackService.get(this).seekToProgress(message.arg1);
updateElapsedTime();
break;
default:
return super.handleMessage(message);
}
return true;
}
/**
* Update the current song duration fields.
*
* @param duration The new duration, in milliseconds.
*/
private void setDuration(long duration) {
mDuration = duration;
mDurationView.setText(DateUtils.formatElapsedTime(mTimeBuilder, duration / 1000));
}
/**
* Update seek bar progress and schedule another update in one second
*/
private void updateElapsedTime() {
long position = PlaybackService.hasInstance() ? PlaybackService.get(this).getPosition() : 0;
if (!mSeekBarTracking) {
long duration = mDuration;
mSeekBar.setProgress(duration == 0 ? 0 : (int)(1000 * position / duration));
}
mElapsedView.setText(DateUtils.formatElapsedTime(mTimeBuilder, position / 1000));
if (!mPaused && (mState & PlaybackService.FLAG_PLAYING) != 0) {
// Try to update right after the duration increases by one second
long next = 1050 - position % 1000;
mUiHandler.removeMessages(MSG_UPDATE_PROGRESS);
mUiHandler.sendEmptyMessageDelayed(MSG_UPDATE_PROGRESS, next);
}
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
mElapsedView.setText(DateUtils.formatElapsedTime(mTimeBuilder, progress * mDuration / 1000000));
mUiHandler.removeMessages(MSG_SEEK_TO_PROGRESS);
mUiHandler.sendMessageDelayed(mUiHandler.obtainMessage(MSG_SEEK_TO_PROGRESS, progress, 0), 150);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mSeekBarTracking = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mSeekBarTracking = false;
}
/**
* Called by SlidingView to signal a visibility change.
* Toggles the visibility of menu items
*
* @param expanded true if slide fully expanded
*/
@Override
public void onSlideFullyExpanded(boolean expanded) {
if (mMenu == null)
return; // not initialized yet
final int[] slide_visible = {MENU_HIDE_QUEUE, MENU_CLEAR_QUEUE, MENU_EMPTY_QUEUE, MENU_SAVE_QUEUE_AS_PLAYLIST};
final int[] slide_hidden = {MENU_SHOW_QUEUE, MENU_SORT, MENU_DELETE, MENU_ENQUEUE_ALBUM, MENU_ENQUEUE_ARTIST, MENU_ENQUEUE_GENRE};
for (int id : slide_visible) {
MenuItem item = mMenu.findItem(id);
if (item != null)
item.setVisible(expanded);
}
for (int id : slide_hidden) {
MenuItem item = mMenu.findItem(id);
if (item != null)
item.setVisible(!expanded);
}
}
}