New animated PlayPauseDrawable

This commit is contained in:
Karim Abou Zeid 2015-04-08 22:09:01 +02:00
commit a27e5c6379
20 changed files with 282 additions and 246 deletions

View file

@ -23,8 +23,8 @@ android {
applicationId "com.kabouzeid.gramophone"
minSdkVersion 16
targetSdkVersion 22
versionCode 10
versionName "0.9.6b"
versionCode 12
versionName "0.9.6.2b"
}
compileOptions {
@ -46,7 +46,7 @@ dependencies {
compile('com.crashlytics.sdk.android:crashlytics:2.2.1@aar') {
transitive = true;
}
compile 'com.afollestad:material-dialogs:0.6.4.1'
compile 'com.afollestad:material-dialogs:0.7.0.1'
compile 'com.android.support:appcompat-v7:22.0.0'
compile 'com.android.support:recyclerview-v7:22.0.0'
compile 'com.android.support:gridlayout-v7:22.0.0'

View file

@ -1,10 +1,6 @@
package com.kabouzeid.gramophone.helper;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import com.afollestad.materialdialogs.MaterialDialog;
import com.kabouzeid.gramophone.R;
@ -25,41 +21,21 @@ public class CreatePlaylistDialogHelper {
}
public static MaterialDialog getDialog(final Context context, final List<Song> songs) {
final EditText editText = new EditText(context);
ViewGroup layout = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.dialog_empty_frame, null);
if (editText.getParent() != null) {
((ViewGroup) editText.getParent()).removeView(editText);
}
layout.addView(editText, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
return new MaterialDialog.Builder(context)
.title(context.getResources().getString(R.string.action_new_playlist))
.customView(layout, false)
.positiveText(context.getResources().getString(R.string.ok))
.negativeText(context.getResources().getString(R.string.cancel))
.callback(new MaterialDialog.ButtonCallback() {
.title(R.string.action_new_playlist)
.positiveText(R.string.ok)
.negativeText(R.string.cancel)
.input("", "", new MaterialDialog.InputCallback() {
@Override
public void onPositive(MaterialDialog dialog) {
super.onPositive(dialog);
final String playlistName = editText.getText().toString();
if (!playlistName.trim().equals("")) {
dialog.dismiss();
final int playlistId = PlaylistsUtil.createPlaylist(context, playlistName);
if (playlistId != -1) {
if (songs != null) {
public void onInput(MaterialDialog materialDialog, CharSequence charSequence) {
if (!charSequence.toString().trim().equals("")) {
final int playlistId = PlaylistsUtil.createPlaylist(context, charSequence.toString());
if (playlistId != -1 && songs != null) {
PlaylistsUtil.addToPlaylist(context, songs, playlistId);
}
}
}
}
@Override
public void onNegative(MaterialDialog dialog) {
super.onNegative(dialog);
dialog.dismiss();
}
}
)
})
.build();
}

View file

@ -15,36 +15,18 @@ import com.kabouzeid.gramophone.util.PlaylistsUtil;
*/
public class RenamePlaylistDialogHelper {
public static MaterialDialog getDialog(final Context context, final int playlistId) {
final EditText editText = new EditText(context);
ViewGroup layout = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.dialog_empty_frame, null);
if (editText.getParent() != null) {
((ViewGroup) editText.getParent()).removeView(editText);
}
editText.setText(PlaylistsUtil.getNameForPlaylist(context, playlistId));
layout.addView(editText, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
return new MaterialDialog.Builder(context)
.title(context.getResources().getString(R.string.rename_playlist))
.customView(layout, false)
.positiveText(context.getResources().getString(R.string.ok))
.negativeText(context.getResources().getString(R.string.cancel))
.callback(new MaterialDialog.ButtonCallback() {
.title(R.string.rename_playlist)
.positiveText(R.string.ok)
.negativeText(R.string.cancel)
.input("", PlaylistsUtil.getNameForPlaylist(context, playlistId), new MaterialDialog.InputCallback() {
@Override
public void onPositive(MaterialDialog dialog) {
super.onPositive(dialog);
final String playlistName = editText.getText().toString();
if (!playlistName.trim().equals("")) {
PlaylistsUtil.renamePlaylist(context, playlistId, playlistName);
public void onInput(MaterialDialog materialDialog, CharSequence charSequence) {
if (!charSequence.toString().trim().equals("")) {
PlaylistsUtil.renamePlaylist(context, playlistId, charSequence.toString());
}
}
@Override
public void onNegative(MaterialDialog dialog) {
super.onNegative(dialog);
dialog.dismiss();
}
}
)
})
.build();
}
}

View file

@ -163,6 +163,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
break;
case ACTION_QUIT:
killEverythingAndReleaseResources();
break;
}
}
}

View file

@ -1,6 +1,5 @@
package com.kabouzeid.gramophone.ui.activities.base;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.util.Pair;
import android.util.Log;
@ -14,6 +13,7 @@ import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.misc.SmallOnGestureListener;
import com.kabouzeid.gramophone.model.MusicRemoteEvent;
import com.kabouzeid.gramophone.ui.widget.PlayPauseDrawable;
import com.kabouzeid.gramophone.util.NavigationUtil;
import com.melnykov.fab.FloatingActionButton;
import com.squareup.otto.Subscribe;
@ -23,7 +23,9 @@ import com.squareup.otto.Subscribe;
*/
public abstract class AbsFabActivity extends AbsBaseActivity {
public static final String TAG = AbsFabActivity.class.getSimpleName();
private FloatingActionButton fab;
private PlayPauseDrawable playPauseDrawable;
private Object busEventListener = new Object() {
@Subscribe
public void onBusEvent(MusicRemoteEvent event) {
@ -42,9 +44,11 @@ public abstract class AbsFabActivity extends AbsBaseActivity {
}
private void setUpFab() {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
getFab().setImageResource(R.drawable.ic_pause_resume);
if (playPauseDrawable == null) {
playPauseDrawable = new PlayPauseDrawable(this);
}
getFab().setImageDrawable(playPauseDrawable);
updateFabState();
final GestureDetector gestureDetector = new GestureDetector(this, new SmallOnGestureListener() {
@Override
@ -80,9 +84,9 @@ public abstract class AbsFabActivity extends AbsBaseActivity {
private void updateFabState() {
if (MusicPlayerRemote.isPlaying()) {
setFabPause();
playPauseDrawable.setPause();
} else {
setFabPlay();
playPauseDrawable.setPlay();
}
}
@ -150,17 +154,11 @@ public abstract class AbsFabActivity extends AbsBaseActivity {
}
}
private void setFabPlay(){
getFab().setSelected(true);
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP){
getFab().setImageResource(R.drawable.ic_play_arrow_white_24dp);
}
private void setFabPlay() {
playPauseDrawable.animatedPlay();
}
private void setFabPause(){
getFab().setSelected(false);
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP){
getFab().setImageResource(R.drawable.ic_pause_white_24dp);
}
private void setFabPause() {
playPauseDrawable.animatedPause();
}
}

View file

@ -0,0 +1,219 @@
package com.kabouzeid.gramophone.ui.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.Property;
import android.view.animation.DecelerateInterpolator;
import com.kabouzeid.gramophone.R;
public class PlayPauseDrawable extends Drawable {
private static final long PLAY_PAUSE_ANIMATION_DURATION = 250;
private static final Property<PlayPauseDrawable, Float> PROGRESS =
new Property<PlayPauseDrawable, Float>(Float.class, "progress") {
@Override
public Float get(PlayPauseDrawable d) {
return d.getProgress();
}
@Override
public void set(PlayPauseDrawable d, Float value) {
d.setProgress(value);
}
};
private final Path leftPauseBar = new Path();
private final Path rightPauseBar = new Path();
private final Paint paint = new Paint();
private final float pauseBarWidth;
private final float pauseBarHeight;
private final float pauseBarDistance;
private float width;
private float height;
private float fallBackWidth;
private float fallBackHeight;
private float progress;
private boolean isPlay;
private boolean isPlaySet;
private AnimatorSet animatorSet;
public PlayPauseDrawable(Context context) {
final Resources res = context.getResources();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
pauseBarWidth = res.getDimensionPixelSize(R.dimen.pause_bar_width);
pauseBarHeight = res.getDimensionPixelSize(R.dimen.pause_bar_height);
pauseBarDistance = res.getDimensionPixelSize(R.dimen.pause_bar_distance);
fallBackWidth = res.getDimensionPixelSize(R.dimen.fab_icon_bound_width);
fallBackHeight = res.getDimensionPixelSize(R.dimen.fab_icon_bound_height);
}
@Override
protected void onBoundsChange(final Rect bounds) {
super.onBoundsChange(bounds);
if (bounds.width() > 0 && bounds.height() > 0) {
width = bounds.width();
height = bounds.height();
} else {
width = fallBackWidth;
height = fallBackHeight;
}
}
@Override
public void draw(Canvas canvas) {
leftPauseBar.rewind();
rightPauseBar.rewind();
// The current distance between the two pause bars.
final float barDist = lerp(pauseBarDistance, 0, progress);
// The current width of each pause bar.
final float barWidth = lerp(pauseBarWidth, pauseBarHeight / 2f, progress);
// The current position of the left pause bar's top left coordinate.
final float firstBarTopLeft = lerp(0, barWidth, progress);
// The current position of the right pause bar's top right coordinate.
final float secondBarTopRight = lerp(2 * barWidth + barDist, barWidth + barDist, progress);
// Draw the left pause bar. The left pause bar transforms into the
// top half of the play button triangle by animating the position of the
// rectangle's top left coordinate and expanding its bottom width.
leftPauseBar.moveTo(0, 0);
leftPauseBar.lineTo(firstBarTopLeft, -pauseBarHeight);
leftPauseBar.lineTo(barWidth, -pauseBarHeight);
leftPauseBar.lineTo(barWidth, 0);
leftPauseBar.close();
// Draw the right pause bar. The right pause bar transforms into the
// bottom half of the play button triangle by animating the position of the
// rectangle's top right coordinate and expanding its bottom width.
rightPauseBar.moveTo(barWidth + barDist, 0);
rightPauseBar.lineTo(barWidth + barDist, -pauseBarHeight);
rightPauseBar.lineTo(secondBarTopRight, -pauseBarHeight);
rightPauseBar.lineTo(2 * barWidth + barDist, 0);
rightPauseBar.close();
canvas.save();
// Translate the play button a tiny bit to the right so it looks more centered.
canvas.translate(lerp(0, pauseBarHeight / 8f, progress), 0);
// (1) Pause --> Play: rotate 0 to 90 degrees clockwise.
// (2) Play --> Pause: rotate 90 to 180 degrees clockwise.
final float rotationProgress = isPlay ? 1 - progress : progress;
final float startingRotation = isPlay ? 90 : 0;
canvas.rotate(lerp(startingRotation, startingRotation + 90, rotationProgress), width / 2f, height / 2f);
// Position the pause/play button in the center of the drawable's bounds.
canvas.translate(width / 2f - ((2 * barWidth + barDist) / 2f), height / 2f + (pauseBarHeight / 2f));
// Draw the two bars that form the animated pause/play button.
canvas.drawPath(leftPauseBar, paint);
canvas.drawPath(rightPauseBar, paint);
canvas.restore();
}
private Animator getPausePlayAnimator() {
isPlaySet = !isPlaySet;
final Animator anim = ObjectAnimator.ofFloat(this, PROGRESS, isPlay ? 1 : 0, isPlay ? 0 : 1);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
isPlay = !isPlay;
}
});
return anim;
}
public boolean isPlay() {
return isPlaySet;
}
private void setProgress(float progress) {
this.progress = progress;
invalidateSelf();
}
private float getProgress() {
return progress;
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
invalidateSelf();
}
@Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
invalidateSelf();
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
/**
* Linear interpolate between a and b with parameter t.
*/
private static float lerp(float a, float b, float t) {
return a + (b - a) * t;
}
public void animatedPlay() {
if (!isPlaySet) {
togglePlayPause();
}
}
public void animatedPause() {
if (isPlaySet) {
togglePlayPause();
}
}
public void setPlay() {
isPlaySet = true;
isPlay = true;
setProgress(1);
}
public void setPause() {
isPlaySet = false;
isPlay = false;
setProgress(0);
}
public void togglePlayPause() {
if (animatorSet != null) {
animatorSet.cancel();
}
animatorSet = new AnimatorSet();
final Animator pausePlayAnim = getPausePlayAnimator();
animatorSet.setInterpolator(new DecelerateInterpolator());
animatorSet.setDuration(PLAY_PAUSE_ANIMATION_DURATION);
animatorSet.playTogether(pausePlayAnim);
animatorSet.start();
}
}

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="90" />
</set>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="pathData"
android:valueFrom="@string/drawable_vector_pause_left"
android:valueTo="@string/drawable_vector_resume_left"
android:valueType="pathType" />

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="pathData"
android:valueFrom="@string/drawable_vector_pause_right"
android:valueTo="@string/drawable_vector_resume_right"
android:valueType="pathType" />

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="rotation"
android:valueFrom="90"
android:valueTo="180" />
</set>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="pathData"
android:valueFrom="@string/drawable_vector_resume_left"
android:valueTo="@string/drawable_vector_pause_left"
android:valueType="pathType" />

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="pathData"
android:valueFrom="@string/drawable_vector_resume_right"
android:valueTo="@string/drawable_vector_pause_right"
android:valueType="pathType" />

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:name="rotationGroup"
android:pivotX="12"
android:pivotY="12">
<path
android:name="left"
android:fillColor="@android:color/white"
android:pathData="@string/drawable_vector_pause_left" />
<path
android:name="right"
android:fillColor="@android:color/white"
android:pathData="@string/drawable_vector_pause_right" />
</group>
</vector>

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize="true">
<item
android:id="@+id/pressed_selected"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@drawable/ic_resume" />
<item
android:id="@+id/action_resume_state"
android:drawable="@drawable/ic_resume"
android:state_selected="true" />
<item
android:id="@+id/action_pause_state"
android:drawable="@drawable/ic_pause" />
<transition
android:fromId="@id/pressed_selected"
android:toId="@id/action_resume_state"
android:drawable="@drawable/ic_pause_to_ic_resume" />
<transition
android:fromId="@id/pressed_selected"
android:toId="@id/action_pause_state"
android:drawable="@drawable/ic_resume_to_ic_pause" />
</animated-selector>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_pause">
<target
android:name="rotationGroup"
android:animation="@anim/drawable_pause_to_resume_group" />
<target
android:name="left"
android:animation="@anim/drawable_pause_to_resume_left" />
<target
android:name="right"
android:animation="@anim/drawable_pause_to_resume_right" />
</animated-vector>

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:name="rotationGroup"
android:pivotX="12"
android:pivotY="12"
android:rotation="90">
<path
android:name="left"
android:fillColor="@android:color/white"
android:pathData="@string/drawable_vector_resume_left" />
<path
android:name="right"
android:fillColor="@android:color/white"
android:pathData="@string/drawable_vector_resume_right" />
</group>
</vector>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_resume">
<target
android:name="rotationGroup"
android:animation="@anim/drawable_resume_to_pause_group" />
<target
android:name="left"
android:animation="@anim/drawable_resume_to_pause_left" />
<target
android:name="right"
android:animation="@anim/drawable_resume_to_pause_right" />
</animated-vector>

View file

@ -9,11 +9,11 @@
android:id="@+id/file_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:textAppearance="?android:textAppearanceMedium"/>
android:textAppearance="?android:textAppearanceMedium"
android:textSize="16sp"/>
<TextView
@ -23,61 +23,62 @@
android:fontFamily="sans-serif"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:textAppearance="?android:textAppearanceMedium"/>
android:textAppearance="?android:textAppearanceMedium"
android:textSize="16sp"/>
<TextView
android:id="@+id/file_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:textAppearance="?android:textAppearanceMedium"/>
android:textAppearance="?android:textAppearanceMedium"
android:textSize="16sp"/>
<TextView
android:id="@+id/file_format"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:textAppearance="?android:textAppearanceMedium"/>
android:textAppearance="?android:textAppearanceMedium"
android:textSize="16sp"/>
<TextView
android:id="@+id/track_length"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:textAppearance="?android:textAppearanceMedium"/>
android:textAppearance="?android:textAppearanceMedium"
android:textSize="16sp"/>
<TextView
android:id="@+id/bitrate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:textAppearance="?android:textAppearanceMedium"/>
android:textAppearance="?android:textAppearanceMedium"
android:textSize="16sp"/>
<TextView
android:id="@+id/sampling_rate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:textAppearance="?android:textAppearanceMedium"/>
android:textAppearance="?android:textAppearanceMedium"
android:textSize="16sp"/>
</LinearLayout>

View file

@ -46,4 +46,11 @@ http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout
-->
<dimen name="widget_margin">8dp</dimen>
<dimen name="app_widget_small_artwork_height">64dp</dimen>
<dimen name="pause_bar_width">5dp</dimen>
<dimen name="pause_bar_distance">4dp</dimen>
<dimen name="pause_bar_height">14dp</dimen>
<dimen name="fab_icon_bound_width">32dp</dimen>
<dimen name="fab_icon_bound_height">36dp</dimen>
</resources>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="drawable_vector_pause_left">M6,5 l4,0 0,14 -4,0 z</string>
<string name="drawable_vector_pause_right">M14,5 l4,0 0,14 -4,0 z</string>
<string name="drawable_vector_resume_left">M12,5 l0,0 0,11 -7,0 z</string>
<string name="drawable_vector_resume_right">M12,5 l0,0 7,11 -7,0 z</string>
</resources>