diff options
author | Linus Lee <llee@cyngn.com> | 2015-07-27 17:07:05 -0700 |
---|---|---|
committer | Linus Lee <llee@cyngn.com> | 2015-08-11 18:25:59 -0700 |
commit | 9f3fa0b5a03e69b1896f86ceff88357fb7cdab48 (patch) | |
tree | 2c7d0ec4c9e79fdb54189b069157bd4f9716d16c /src | |
parent | dd33b6ea6558d6934766337e3e6e7f709b63d403 (diff) | |
download | android_packages_apps_Eleven-9f3fa0b5a03e69b1896f86ceff88357fb7cdab48.tar.gz android_packages_apps_Eleven-9f3fa0b5a03e69b1896f86ceff88357fb7cdab48.tar.bz2 android_packages_apps_Eleven-9f3fa0b5a03e69b1896f86ceff88357fb7cdab48.zip |
Add ability to seek through track with circular motion
With this patch, if you do a circular gesture motion
on the large play button, you can seek the track instead
of using the long press of the next/previous buttons
Also change the progress bar to 1000 to allow for smaller updates
Change-Id: I38512b2439227686bc6363e77b1858dbb5764a9f
Diffstat (limited to 'src')
4 files changed, 211 insertions, 33 deletions
diff --git a/src/com/cyanogenmod/eleven/ui/activities/HomeActivity.java b/src/com/cyanogenmod/eleven/ui/activities/HomeActivity.java index d946039..a66c6c0 100644 --- a/src/com/cyanogenmod/eleven/ui/activities/HomeActivity.java +++ b/src/com/cyanogenmod/eleven/ui/activities/HomeActivity.java @@ -38,6 +38,7 @@ import com.cyanogenmod.eleven.R; import com.cyanogenmod.eleven.cache.ImageFetcher; import com.cyanogenmod.eleven.ui.fragments.AlbumDetailFragment; import com.cyanogenmod.eleven.ui.fragments.ArtistDetailFragment; +import com.cyanogenmod.eleven.ui.fragments.AudioPlayerFragment; import com.cyanogenmod.eleven.ui.fragments.IChildFragment; import com.cyanogenmod.eleven.ui.fragments.ISetupActionBar; import com.cyanogenmod.eleven.ui.fragments.PlaylistDetailFragment; @@ -215,7 +216,11 @@ public class HomeActivity extends SlidingPanelActivity implements color = getResources().getColor(R.color.visualizer_fill_color); } - getAudioPlayerFragment().setVisualizerColor(color); + // check for null since updatestatusBarColor is a async task + AudioPlayerFragment fragment = getAudioPlayerFragment(); + if (fragment != null) { + fragment.setVisualizerColor(color); + } } private void updateStatusBarColor(int color) { diff --git a/src/com/cyanogenmod/eleven/ui/fragments/AudioPlayerFragment.java b/src/com/cyanogenmod/eleven/ui/fragments/AudioPlayerFragment.java index efeaa62..e54eabd 100644 --- a/src/com/cyanogenmod/eleven/ui/fragments/AudioPlayerFragment.java +++ b/src/com/cyanogenmod/eleven/ui/fragments/AudioPlayerFragment.java @@ -156,8 +156,6 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection { private boolean mIsPaused = false; - private boolean mFromTouch = false; - @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -352,6 +350,7 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection { */ private void initPlaybackControls() { mPlayPauseProgressButton = (PlayPauseProgressButton)mRootView.findViewById(R.id.playPauseProgressButton); + mPlayPauseProgressButton.setDragEnabled(true); mShuffleButton = (ShuffleButton)mRootView.findViewById(R.id.action_button_shuffle); mRepeatButton = (RepeatButton)mRootView.findViewById(R.id.action_button_repeat); mPreviousButton = (RepeatingImageButton)mRootView.findViewById(R.id.action_button_previous); @@ -538,49 +537,48 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection { } private void refreshCurrentTimeText(final long pos) { - mCurrentTime.setText(MusicUtils.makeShortTimeString(getActivity(), pos / 1000)); + if (mPlayPauseProgressButton.isDragging()) { + mCurrentTime.setText(MusicUtils.makeShortTimeString(getActivity(), + mPlayPauseProgressButton.getDragProgressInMs() / 1000)); + } else { + mCurrentTime.setText(MusicUtils.makeShortTimeString(getActivity(), pos / 1000)); + } } /* Used to update the current time string */ private long refreshCurrentTime() { if (mService == null) { - return 500; + return MusicUtils.UPDATE_FREQUENCY_MS; } try { final long pos = MusicUtils.position(); if (pos >= 0 && MusicUtils.duration() > 0) { refreshCurrentTimeText(pos); - if (mFromTouch) { - return 500; + if (mPlayPauseProgressButton.isDragging()) { + mCurrentTime.setVisibility(View.VISIBLE); + return MusicUtils.UPDATE_FREQUENCY_FAST_MS; } else if (MusicUtils.isPlaying()) { mCurrentTime.setVisibility(View.VISIBLE); + + // calculate the number of milliseconds until the next full second, + // so the counter can be updated at just the right time + return Math.max(20, 1000 - pos % 1000); } else { // blink the counter final int vis = mCurrentTime.getVisibility(); mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE); - return 500; } } else { mCurrentTime.setText("--:--"); } - - // calculate the number of milliseconds until the next full second, - // so - // the counter can be updated at just the right time - final long remaining = 1000 - pos % 1000; - if (remaining < 20) { - return 20; - } - - return remaining; } catch (final Exception ignored) { if (ignored.getMessage() != null) { Log.e(TAG, ignored.getMessage()); } } - return 500; + return MusicUtils.UPDATE_FREQUENCY_MS; } /** diff --git a/src/com/cyanogenmod/eleven/utils/MusicUtils.java b/src/com/cyanogenmod/eleven/utils/MusicUtils.java index 6279a78..38e9ac0 100644 --- a/src/com/cyanogenmod/eleven/utils/MusicUtils.java +++ b/src/com/cyanogenmod/eleven/utils/MusicUtils.java @@ -87,6 +87,9 @@ public final class MusicUtils { public static final String MUSIC_ONLY_SELECTION = MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"; //$NON-NLS-2$ + public static final long UPDATE_FREQUENCY_MS = 500; + public static final long UPDATE_FREQUENCY_FAST_MS = 30; + static { mConnectionMap = new WeakHashMap<Context, ServiceBinder>(); sEmptyList = new long[0]; diff --git a/src/com/cyanogenmod/eleven/widgets/PlayPauseProgressButton.java b/src/com/cyanogenmod/eleven/widgets/PlayPauseProgressButton.java index 5c086be..8b8788f 100644 --- a/src/com/cyanogenmod/eleven/widgets/PlayPauseProgressButton.java +++ b/src/com/cyanogenmod/eleven/widgets/PlayPauseProgressButton.java @@ -1,5 +1,6 @@ /* -* Copyright (C) 2014 The CyanogenMod Project +* Copyright (c) 2013, The Linux Foundation. All rights reserved. +* Copyright (C) 2015 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +18,9 @@ package com.cyanogenmod.eleven.widgets; import android.content.Context; import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.ViewConfiguration; import android.widget.FrameLayout; import android.widget.ProgressBar; @@ -30,12 +34,28 @@ import com.cyanogenmod.eleven.utils.MusicUtils; * updates while the activity/fragment is not visible */ public class PlayPauseProgressButton extends FrameLayout { - private static final long UPDATE_FREQUENCY_MS = 500; + private static String TAG = PlayPauseProgressButton.class.getSimpleName(); + private static boolean DEBUG = false; + private static final int REVOLUTION_IN_DEGREES = 360; + private static final int HALF_REVOLUTION_IN_DEGREES = REVOLUTION_IN_DEGREES / 2; + private ProgressBar mProgressBar; private PlayPauseButton mPlayPauseButton; private Runnable mUpdateProgress; private boolean mPaused; + private final int mSmallDistance; + private float mDragPercentage = 0.0f; + private boolean mDragEnabled = false; + private boolean mDragging = false; + private float mDownAngle; + private float mDragAngle; + private float mDownX; + private float mDownY; + private int mWidth; + private long mCurrentSongDuration; + private long mCurrentSongProgress; + public PlayPauseProgressButton(Context context, AttributeSet attrs) { super(context, attrs); @@ -44,6 +64,8 @@ public class PlayPauseProgressButton extends FrameLayout { // set paused to false since we shouldn't be typically created while not visible mPaused = false; + + mSmallDistance = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override @@ -94,6 +116,27 @@ public class PlayPauseProgressButton extends FrameLayout { setVisibility(GONE); } + /** + * Sets whether the user can drag the progress in a circular motion to seek the track + */ + public void setDragEnabled(boolean enabled) { + mDragEnabled = enabled; + } + + /** + * @return true if the user is actively dragging to seek + */ + public boolean isDragging() { + return mDragEnabled && mDragging; + } + + /** + * @return how far the user has dragged in the track in ms + */ + public long getDragProgressInMs() { + return (long)(mDragPercentage * mCurrentSongDuration); + } + @Override public void setEnabled(boolean enabled) { // if the enabled state isn't changed, quit @@ -158,18 +201,17 @@ public class PlayPauseProgressButton extends FrameLayout { * Updates the state of the progress bar and the play pause button */ private void updateState() { - final long duration = MusicUtils.duration(); - - if (duration > 0) { - final long pos = MusicUtils.position(); - - int progress = (int) (mProgressBar.getMax() * pos / duration); - mProgressBar.setProgress(progress); - } else { - // this is when there are no tracks loaded or some kind of error condition - mProgressBar.setProgress(0); + mCurrentSongDuration = MusicUtils.duration(); + mCurrentSongProgress = MusicUtils.position(); + + int progress = 0; + if (isDragging()) { + progress = (int) (mDragPercentage * mProgressBar.getMax()); + } else if (mCurrentSongDuration > 0) { + progress = (int) (mProgressBar.getMax() * mCurrentSongProgress / mCurrentSongDuration); } + mProgressBar.setProgress(progress); mPlayPauseButton.updateState(); } @@ -182,7 +224,8 @@ public class PlayPauseProgressButton extends FrameLayout { @Override public void run() { updateState(); - postDelayed(mUpdateProgress, UPDATE_FREQUENCY_MS); + postDelayed(mUpdateProgress, isDragging() ? MusicUtils.UPDATE_FREQUENCY_FAST_MS + : MusicUtils.UPDATE_FREQUENCY_MS); } }; } @@ -191,7 +234,7 @@ public class PlayPauseProgressButton extends FrameLayout { removeCallbacks(mUpdateProgress); // post ourselves as a delayed - postDelayed(mUpdateProgress, UPDATE_FREQUENCY_MS); + post(mUpdateProgress); } /** @@ -202,4 +245,133 @@ public class PlayPauseProgressButton extends FrameLayout { removeCallbacks(mUpdateProgress); } } + + @Override + protected void onSizeChanged(int w, int h, int oldW, int oldH) { + mWidth = Math.min(w, h); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (!mDragEnabled) { + return false; + } + + return onTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final float x = event.getX(); + final float y = event.getY(); + + if (!mDragEnabled || mCurrentSongDuration <= 0) { + return false; + } + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mDownX = event.getX(); + mDownY = event.getY(); + mDownAngle = angle(mDownX, mDownY); + mDragAngle = REVOLUTION_IN_DEGREES + * (mCurrentSongProgress / (float) mCurrentSongDuration); + mDragPercentage = mDragAngle / REVOLUTION_IN_DEGREES; + mDragging = false; + break; + case MotionEvent.ACTION_MOVE: + // if the user has moved a certain distance + if (Math.sqrt(Math.pow(event.getX() - mDownX, 2) + + Math.pow(event.getY() - mDownY, 2)) < mSmallDistance) { + return false; + } + + // if we weren't previously dragging, immediately kick off an update to reflect + // the change faster + if (!mDragging) { + postUpdate(); + } + + mDragging = true; + getParent().requestDisallowInterceptTouchEvent(true); + + // calculate the amount of angle we've moved + final float deltaAngle = getDelta(x, y); + mDragAngle = cropAngle(mDragAngle + deltaAngle); + mDragPercentage = mDragAngle / REVOLUTION_IN_DEGREES; + + if (DEBUG) { + Log.d(TAG, "Delta Angle: " + deltaAngle + ", Target Angle: " + mDownAngle); + } + + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + // if we were dragging, seek to where we dragged to + if (mDragging) { + MusicUtils.seek((long)(mDragPercentage * mCurrentSongDuration)); + } + mDragging = false; + default: + break; + } + return mDragging; + } + + /** + * Crops the angle between 0 and 360 - if the angle is < 0, it will return 0, if it is more than + * 360 it will return 360 + */ + private static float cropAngle(float angle) { + return Math.min(REVOLUTION_IN_DEGREES, Math.max(0.0f, angle)); + } + + /** + * Wraps the angle between -180 and 180. This assumes that the passed in + * angle is >= -360 and <= 360 + */ + private static float wrapHalfRevolution(float angle) { + if (angle < -HALF_REVOLUTION_IN_DEGREES) { + return angle + REVOLUTION_IN_DEGREES; + } else if (angle > HALF_REVOLUTION_IN_DEGREES) { + return angle - REVOLUTION_IN_DEGREES; + } + + return angle; + } + + /** + * Gets the change in angle from the down angle and updates the down angle to the current angle + */ + private float getDelta(float x, float y) { + float angle = angle(x, y); + float deltaAngle = wrapHalfRevolution(angle - mDownAngle); + mDownAngle = angle; + return deltaAngle; + } + + /** + * Calculates the angle at the point passed in based on the center of the button + */ + private float angle(float x, float y) { + float center = mWidth / 2.0f; + x -= center; + y -= center; + + if (x == 0.0f) { + if (y > 0.0f) { + return 180.0f; + } else { + return 0.0f; + } + } + + float angle = (float) (Math.atan(y / x) / Math.PI * 180.0); + if (x > 0.0f) { + angle += 90; + } else { + angle += 270; + } + return angle; + } } |