Twidere-App-Android-Twitter.../twidere/src/main/java/org/mariotaku/twidere/graphic/like/LikeAnimationDrawable.java

436 lines
15 KiB
Java

package org.mariotaku.twidere.graphic.like;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.v7.widget.android.support.v7.view.menu.TwidereActionMenuItemView;
import android.util.Property;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import org.mariotaku.twidere.graphic.iface.DoNotWrapDrawable;
import org.mariotaku.twidere.graphic.like.layer.AnimationLayerDrawable;
import org.mariotaku.twidere.graphic.like.layer.CircleLayerDrawable;
import org.mariotaku.twidere.graphic.like.layer.ParticleLayerDrawable;
import org.mariotaku.twidere.graphic.like.layer.ScalableDrawable;
import org.mariotaku.twidere.graphic.like.layer.ShineLayerDrawable;
import org.mariotaku.twidere.graphic.like.palette.FavoritePalette;
import org.mariotaku.twidere.graphic.like.palette.LikePalette;
import java.lang.ref.WeakReference;
/**
* Created by mariotaku on 15/11/4.
*/
public class LikeAnimationDrawable extends Drawable implements Animatable, Drawable.Callback,
DoNotWrapDrawable, TwidereActionMenuItemView.IgnoreTinting {
@NonNull
private LikeAnimationState mState;
private boolean mMutated;
public LikeAnimationDrawable(final Drawable icon, @ColorInt final int defaultColor,
@ColorInt final int activatedColor,
@Style final int style) {
mState = new LikeAnimationState(icon, defaultColor, activatedColor, style, this);
}
LikeAnimationDrawable(@NonNull LikeAnimationState state) {
mState = state;
}
@Override
public void start() {
if (mState.mCurrentAnimator != null) return;
final AnimatorSet animatorSet = new AnimatorSet();
final AnimationLayerDrawable particleLayer = mState.mParticleLayer;
final AnimationLayerDrawable circleLayer = mState.mCircleLayer;
final ScalableDrawable iconLayer = mState.mIconDrawable;
switch (mState.mStyle) {
case Style.LIKE: {
setupLikeAnimation(animatorSet, particleLayer, circleLayer, iconLayer);
break;
}
case Style.FAVORITE: {
setupFavoriteAnimation(animatorSet, particleLayer, circleLayer, iconLayer);
break;
}
}
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
resetState();
}
@Override
public void onAnimationCancel(Animator animation) {
mState.mCurrentAnimator = null;
}
@Override
public void onAnimationEnd(Animator animation) {
mState.mCurrentAnimator = null;
if (mState.mListenerRef == null) return;
final OnLikedListener listener = mState.mListenerRef.get();
if (listener == null) return;
if (!listener.onLiked()) {
resetState();
}
}
private void resetState() {
setColorFilter(mState.mDefaultColor, PorterDuff.Mode.SRC_ATOP);
particleLayer.setProgress(-1);
}
});
animatorSet.start();
mState.mCurrentAnimator = animatorSet;
}
private void setupFavoriteAnimation(final AnimatorSet animatorSet, final Layer particleLayer,
final Layer circleLayer, final ScalableDrawable iconLayer) {
setupLikeAnimation(animatorSet, particleLayer, circleLayer, iconLayer);
}
private void setupLikeAnimation(final AnimatorSet animatorSet, final Layer particleLayer,
final Layer circleLayer, final ScalableDrawable iconLayer) {
final long duration = mState.mDuration;
final long scaleDownDuration = Math.round(1f / 24f * duration);
final long ovalExpandDuration = Math.round(4f / 24f * duration);
final long iconExpandOffset = Math.round(6f / 24f * duration);
final long iconExpandDuration = Math.round(8f / 24f * duration);
final long iconNormalDuration = Math.round(4f / 24f * duration);
final long particleExpandDuration = Math.round(12f / 24f * duration);
final long circleExplodeDuration = Math.round(5f / 24f * duration);
final ObjectAnimator iconScaleDown = ObjectAnimator.ofFloat(iconLayer, IconScaleProperty.SINGLETON, 1, 0);
iconScaleDown.setDuration(scaleDownDuration);
iconScaleDown.setInterpolator(new AccelerateInterpolator(2));
iconScaleDown.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setColorFilter(mState.mDefaultColor, PorterDuff.Mode.SRC_ATOP);
}
});
final ObjectAnimator ovalExpand = ObjectAnimator.ofFloat(circleLayer, LayerProgressProperty.SINGLETON, 0, 0.5f);
ovalExpand.setDuration(ovalExpandDuration);
final ObjectAnimator iconExpand = ObjectAnimator.ofFloat(iconLayer, IconScaleProperty.SINGLETON, 0, 1.25f);
iconExpand.setDuration(iconExpandDuration);
iconExpand.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setColorFilter(mState.mActivatedColor, PorterDuff.Mode.SRC_ATOP);
}
});
final ObjectAnimator particleExplode = ObjectAnimator.ofFloat(particleLayer, LayerProgressProperty.SINGLETON, 0, 0.5f);
particleExplode.setDuration(iconExpandDuration);
final ObjectAnimator iconNormal = ObjectAnimator.ofFloat(iconLayer, IconScaleProperty.SINGLETON, 1.25f, 1);
iconNormal.setDuration(iconNormalDuration);
final ObjectAnimator circleExplode = ObjectAnimator.ofFloat(circleLayer, LayerProgressProperty.SINGLETON, 0.5f, 0.95f, 0.95f, 1);
circleExplode.setDuration(circleExplodeDuration);
circleExplode.setInterpolator(new DecelerateInterpolator());
final ObjectAnimator particleFade = ObjectAnimator.ofFloat(particleLayer, LayerProgressProperty.SINGLETON, 0.5f, 1);
particleFade.setDuration(particleExpandDuration);
animatorSet.play(iconScaleDown);
animatorSet.play(ovalExpand).after(iconScaleDown);
animatorSet.play(iconExpand).after(iconExpandOffset);
animatorSet.play(particleExplode).after(iconExpandOffset);
animatorSet.play(circleExplode).after(iconExpandOffset);
animatorSet.play(iconNormal).after(iconExpand);
animatorSet.play(particleFade).after(iconExpand);
}
@Override
public void stop() {
if (mState.mCurrentAnimator == null) return;
mState.mCurrentAnimator.cancel();
}
@Override
public boolean isRunning() {
return mState.mCurrentAnimator != null && mState.mCurrentAnimator.isRunning();
}
public long getDuration() {
return mState.mDuration;
}
public void setDuration(long duration) {
mState.mDuration = duration;
}
public void setOnLikedListener(OnLikedListener listener) {
mState.mListenerRef = new WeakReference<>(listener);
}
@Override
public int getIntrinsicWidth() {
return mState.mIconDrawable.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
return mState.mIconDrawable.getIntrinsicHeight();
}
@Override
public void draw(Canvas canvas) {
mState.mCircleLayer.draw(canvas);
mState.mParticleLayer.draw(canvas);
mState.mIconDrawable.draw(canvas);
}
@Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
mState.setBounds(left, top, right, bottom);
}
@Override
public void setAlpha(int alpha) {
mState.mIconDrawable.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mState.setIconColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void invalidateDrawable(Drawable who) {
invalidateSelf();
}
@Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(Drawable who, Runnable what) {
unscheduleSelf(what);
}
@Override
public ConstantState getConstantState() {
return mState;
}
@Override
public int getChangingConfigurations() {
return super.getChangingConfigurations() | mState.getChangingConfigurations();
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
mState = new LikeAnimationState(mState, this);
mMutated = true;
}
return this;
}
public interface OnLikedListener {
boolean onLiked();
}
@IntDef({Style.LIKE, Style.FAVORITE})
public @interface Style {
int LIKE = 1;
int FAVORITE = 2;
}
static class LikeAnimationState extends ConstantState {
// Default values
private final int mDefaultColor;
private final int mActivatedColor;
@Style
private final int mStyle;
// Layers
private final AnimationLayerDrawable mCircleLayer;
private final AnimationLayerDrawable mParticleLayer;
private final ScalableDrawable mIconDrawable;
private long mDuration = 500;
private AnimatorSet mCurrentAnimator;
private WeakReference<OnLikedListener> mListenerRef;
public LikeAnimationState(final Drawable icon, final int defaultColor, final int activatedColor,
@Style final int style, Callback callback) {
mDefaultColor = defaultColor;
mActivatedColor = activatedColor;
mStyle = style;
final int intrinsicWidth = icon.getIntrinsicWidth();
final int intrinsicHeight = icon.getIntrinsicHeight();
mIconDrawable = new ScalableDrawable(icon);
setIconColorFilter(new PorterDuffColorFilter(defaultColor, PorterDuff.Mode.SRC_ATOP));
final Palette palette;
switch (style) {
case Style.FAVORITE: {
palette = new FavoritePalette();
mParticleLayer = new ShineLayerDrawable(intrinsicWidth, intrinsicHeight,
palette);
break;
}
case Style.LIKE: {
palette = new LikePalette();
mParticleLayer = new ParticleLayerDrawable(intrinsicWidth, intrinsicHeight,
palette);
break;
}
default: {
throw new IllegalArgumentException();
}
}
mParticleLayer.setProgress(-1);
mCircleLayer = new CircleLayerDrawable(intrinsicWidth, intrinsicHeight, palette);
mIconDrawable.setCallback(callback);
mParticleLayer.setCallback(callback);
mCircleLayer.setCallback(callback);
}
public LikeAnimationState(LikeAnimationState state, LikeAnimationDrawable owner) {
mDefaultColor = state.mDefaultColor;
mActivatedColor = state.mActivatedColor;
mStyle = state.mStyle;
mCircleLayer = (AnimationLayerDrawable) clone(state.mCircleLayer, owner);
mParticleLayer = (AnimationLayerDrawable) clone(state.mParticleLayer, owner);
mIconDrawable = (ScalableDrawable) clone(state.mIconDrawable, owner);
}
public void setIconColorFilter(ColorFilter cf) {
mIconDrawable.setColorFilter(cf);
}
private static Drawable clone(Drawable orig, LikeAnimationDrawable owner) {
final Drawable clone = orig.getConstantState().newDrawable();
clone.mutate();
clone.setCallback(owner);
clone.setBounds(orig.getBounds());
return clone;
}
public void setBounds(int left, int top, int right, int bottom) {
mCircleLayer.setBounds(left, top, right, bottom);
mParticleLayer.setBounds(left, top, right, bottom);
mIconDrawable.setBounds(left, top, right, bottom);
}
@NonNull
@Override
public Drawable newDrawable() {
return new LikeAnimationDrawable(this);
}
@Override
public int getChangingConfigurations() {
return mCircleLayer.getChangingConfigurations() |
mParticleLayer.getChangingConfigurations() |
mIconDrawable.getChangingConfigurations();
}
}
static final class IconScaleProperty extends Property<ScalableDrawable, Float> {
static final Property<ScalableDrawable, Float> SINGLETON = new IconScaleProperty();
private IconScaleProperty() {
super(Float.class, "icon_scale");
}
@Override
public void set(ScalableDrawable object, Float value) {
object.setScale(value);
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public Float get(ScalableDrawable object) {
return object.getScale();
}
}
static final class LayerProgressProperty extends Property<Layer, Float> {
static final Property<Layer, Float> SINGLETON = new LayerProgressProperty();
private LayerProgressProperty() {
super(Float.class, "layer_progress");
}
@Override
public void set(Layer object, Float value) {
object.setProgress(value);
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public Float get(Layer object) {
return object.getProgress();
}
}
/**
* Created by mariotaku on 16/2/22.
*/
public interface Layer {
float getProgress();
void setProgress(float progress);
}
/**
* Created by mariotaku on 16/2/22.
*/
public interface Palette {
int getParticleColor(int count, int index, float progress);
int getCircleColor(float progress);
}
}