package carbon.animation;
import android.animation.Animator;
import android.util.StateSet;
import android.view.View;
import android.view.animation.Animation;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
public class StateAnimator {
private final ArrayList<Tuple> mTuples = new ArrayList<>();
private Tuple lastMatch = null;
private Animator runningAnimation = null;
private WeakReference<AnimatedView> viewRef;
public StateAnimator(AnimatedView target) {
setTarget(target);
}
private Animator.AnimatorListener mAnimationListener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (runningAnimation == animation) {
runningAnimation = null;
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
};
/**
* Associates the given Animation with the provided drawable state specs so that it will be run
* when the View's drawable state matches the specs.
*
* @param specs drawable state specs to match against
* @param animation The Animation to run when the specs match
*/
public void addState(int[] specs, Animator animation, Animator.AnimatorListener listener) {
Tuple tuple = new Tuple(specs, animation, listener);
animation.addListener(mAnimationListener);
mTuples.add(tuple);
}
/**
* Returns the current {@link Animation} which is started because of a state
* change.
*
* @return The currently running Animation or null if no Animation is running
*/
Animator getRunningAnimation() {
return runningAnimation;
}
AnimatedView getTarget() {
return viewRef == null ? null : viewRef.get();
}
void setTarget(AnimatedView view) {
final AnimatedView current = getTarget();
if (current == view) {
return;
}
if (current != null) {
clearTarget();
}
if (view != null) {
viewRef = new WeakReference<>(view);
}
}
private void clearTarget() {
final AnimatedView view = getTarget();
final int size = mTuples.size();
for (int i = 0; i < size; i++) {
Animator anim = mTuples.get(i).animation;
if (view.getAnimator() == anim) {
anim.cancel();
}
}
viewRef = null;
lastMatch = null;
runningAnimation = null;
}
/**
* Called by View
*/
public void setState(int[] state) {
Tuple match = null;
final int count = mTuples.size();
for (int i = 0; i < count; i++) {
final Tuple tuple = mTuples.get(i);
if (StateSet.stateSetMatches(tuple.mSpecs, state)) {
match = tuple;
break;
}
}
if (match == lastMatch) {
return;
}
if (lastMatch != null) {
cancel();
}
lastMatch = match;
View view = (View) viewRef.get();
if (match != null && view != null && view.getVisibility() == View.VISIBLE) {
start(match);
}
}
private void start(Tuple match) {
match.getListener().onAnimationStart(match.animation);
runningAnimation = match.animation;
runningAnimation.start();
}
private void cancel() {
if (runningAnimation != null) {
final AnimatedView view = getTarget();
if (view != null && view.getAnimator() == runningAnimation) {
runningAnimation.cancel();
}
runningAnimation = null;
}
}
/**
* @hide
*/
ArrayList<Tuple> getTuples() {
return mTuples;
}
/**
* If there is an animation running for a recent state change, ends it. <p> This causes the
* animation to assign the end value(s) to the View.
*/
public void jumpToCurrentState() {
if (runningAnimation != null) {
final AnimatedView view = getTarget();
if (view != null && view.getAnimator() == runningAnimation) {
runningAnimation.cancel();
}
}
}
static class Tuple {
final int[] mSpecs;
final Animator animation;
private Animator.AnimatorListener listener;
private Tuple(int[] specs, Animator Animation, Animator.AnimatorListener listener) {
mSpecs = specs;
animation = Animation;
this.listener = listener;
}
int[] getSpecs() {
return mSpecs;
}
Animator getAnimation() {
return animation;
}
public Animator.AnimatorListener getListener() {
return listener;
}
}
}