Skip to content

Instantly share code, notes, and snippets.

@MinceMan
Last active August 29, 2015 14:06
Show Gist options
  • Save MinceMan/3664cb880548690b04a2 to your computer and use it in GitHub Desktop.
Save MinceMan/3664cb880548690b04a2 to your computer and use it in GitHub Desktop.
AnimatorChainer with Examples, Android
package com.twotoasters.util.anim;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.view.ViewPropertyAnimator;
import java.util.ArrayList;
/**
* A class which makes it really easy to implement sequencal animations with ViewPropertyAnimators.
* It is simple to make them loop or not loop.
* Supports Api 12+.
*/
public abstract class AnimatorChainer {
private boolean paused;
private ArrayList<AnimatorRunner> chain = new ArrayList<>();
/** Use addAnimatorRunnerToChain() to build your animator chains. **/
protected abstract void buildAnimationChain();
/**
* You need to call cancel on all of your ViewPropertyAnimators here to stop aniamtions.
* Reset the position of any views you need to, to get them into their starting location.
*/
public abstract void cancelAndResetAnimators();
private boolean hasChain() {
return chain != null && chain.size() > 0;
}
/**
* Removes all AnimatorRunners from the chain.
*/
protected void clearChain() {
chain.clear();
}
protected void addAnimatorRunnerToChain(AnimatorRunner runner) {
chain.add(runner);
}
private void moveAnimatorToBackOfRotatingChain(AnimatorRunner animatorRunner) {
chain.remove(animatorRunner);
chain.add(animatorRunner);
}
////////////////
// Life cycle
////////////////
/**
* The animator chain will be rebuilt every time this is called.
*/
public void start() {
paused = false;
clearChain();
buildAnimationChain();
if (hasChain()) {
runNextAnimator();
}
}
/**
* Will resume animations in the chain at the last one after pause.
* Note: if you did not call pause() this will do nothing.
*/
public void resume() {
if (paused && hasChain()) {
runNextAnimator();
paused = false;
}
}
/**
* Call to pause chain.
* Note: that it will not pause in the middle of an animation instead it will just not play the next one.
*/
public void pause() {
paused = true;
}
private void runNextAnimator() {
if (!paused && hasChain()) {
chain.get(0).runAnimator(this);
}
}
private void animationEnded(AnimatorRunner animatorRunner) {
moveAnimatorToBackOfRotatingChain(animatorRunner);
if (animatorRunner.continueToNextAnimation()) runNextAnimator();
}
public abstract static class AnimatorRunner implements AnimatorListener {
private AnimatorChainer chainer;
private boolean continueToNextAnimation = true;
/**
* @return true to continue animating. False will break the chain.
* Default is true.
*/
public boolean continueToNextAnimation() {
return continueToNextAnimation;
}
/**
* @param shouldContinue set to flase to brake the chain and stop animations after this one.
* @return
*/
public AnimatorRunner setContinueToNextAnimation(boolean shouldContinue) {
continueToNextAnimation = shouldContinue;
return this;
}
private void runAnimator(AnimatorChainer chainer) {
this.chainer = chainer;
ViewPropertyAnimator animator = buildAnimator();
animator.setListener(this);
animator.start();
}
/** Animation listeners will not be respected.
* Please override related listener methods.
* Always call super first on those methods.
* Do not start animation yourself, that defeats the purpose. **/
protected abstract ViewPropertyAnimator buildAnimator();
@Override
/** Here for override. Always call super first. **/
public void onAnimationStart(Animator animation) {
// no op
}
@Override
/** Here for override. Always call super first. **/
public void onAnimationEnd(Animator animation) {
if (chainer != null) {
chainer.animationEnded(this);
chainer = null;
}
}
@Override
/** Here for override. Always call super first. **/
public void onAnimationCancel(Animator animation) {
chainer = null;
}
@Override
/** Here for override. Always call super first. **/
public void onAnimationRepeat(Animator animation) {
// no op
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ChainedWidget">
<attr name="drawableFirst" format="reference" />
<attr name="drawableSecond" format="reference" />
<attr name="drawableThird" format="reference" />
</declare-styleable>
</resources>
package com.twotoasters.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import com.twotoasters.R;
import com.twotoasters.util.anim.ExmapleAnimatorChainer;
public class ChainedWidget extends FrameLayout implements OnPreDrawListener {
private Drawable drawableFirst;
private Drawable drawableSecond;
private Drawable drawableThird;
private ImageView imageFirst;
private ImageView imageSecond;
private ImageView imageThird;
private ExampleAnimatorChainer animatorChain;
private boolean hasStartedAnimators;
private boolean hasDrawen;
public ChainedWidget(Context context) {
super(context);
init(null, 0);
}
public ChainedWidget(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public ChainedWidget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
@Override
protected void onDetachedFromWindow() {
stopAndResetAnimation();
super.onDetachedFromWindow();
}
private void init(AttributeSet attrs, int defStyle) {
if (attrs == null) return;
imageFirst = getNewImageView();
imageSecond = getNewImageView();
imageThird = getNewImageView();
addView(imageFirst);
addView(imageSecond);
addView(imageThird);
TypedArray tArray = getContext().obtainStyledAttributes(attrs, R.styleable.ChainedWidget, defStyle, 0);
Drawable drawableFirst = tArray.getDrawable(R.styleable.ChainedWidget_drawableFirst);
Drawable drawableSecond = tArray.getDrawable(R.styleable.ChainedWidget_drawableSecond);
Drawable drawableThird = tArray.getDrawable(R.styleable.ChainedWidget_drawableThird);
setAnimationDrawables(drawableFirst, drawableSecond, drawableThird);
tArray.recycle();
ViewTreeObserver vto = getViewTreeObserver();
if (vto != null) {
vto.addOnPreDrawListener(this);
}
}
private ImageView getNewImageView() {
ImageView imageView = new ImageView(getContext());
imageView.setScaleType(ScaleType.CENTER);
return imageView;
}
public void setAnimationDrawables(Drawable drawableFirst, Drawable drawableSecond, Drawable drawableThird) {
stopAndResetAnimation();
this.drawableFirst = drawableFirst;
this.drawableSecond = drawableSecond;
this.drawableThird = drawableThird;
imageFirst.setImageDrawable(this.drawableFirst);
imageSecond.setImageDrawable(this.drawableSecond);
imageThird.setImageDrawable(this.drawableThird);
animatorChain = new ExampleAnimatorChainer(imageFirst, imageSecond, imageThird);
animatorChain.cancelAndResetAnimators();
}
public boolean hasAnimationDrawables() {
return animatorChain != null
&& drawableFirst != null
&& drawableSecond != null
&& drawableThird != null;
}
public void startAnimation() {
if (!hasAnimationDrawables()) return;
stopAndResetAnimation();
animatorChain.start();
hasStartedAnimators = true;
}
public void stopAndResetAnimation() {
if (!hasAnimationDrawables()) return;
hasStartedAnimators = false;
if (animatorChain != null) animatorChain.cancelAndResetAnimators();
}
@Override
public boolean onPreDraw() {
ViewTreeObserver vto = getViewTreeObserver();
if (vto == null) return true;
vto.removeOnPreDrawListener(this);
if (hasAnimationDrawables()) animatorChain.cancelAndResetAnimators();
if (hasStartedAnimators) startAnimation();
hasDrawen = true;
return true;
}
}
package com.twotoasters.util.anim;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.widget.ImageView;
public class ExampleAnimatorChainer extends AnimatorChainer {
private ImageView imageFirst;
private ImageView imageSecond;
private ImageView imageThird;
public ExampleAnimatorChainer(ImageView imageFirst, ImageView imageSecond, ImageView imageThird) {
this.imageFirst = imageFirst;
this.imageSecond = imageSecond;
this.imageThird = imageThird;
}
@Override
protected void buildAnimationChain() {
addAnimatorRunnerToChain(new ClickDownAnimatorRunner());
addAnimatorRunnerToChain(new ClickUpAnimatorRunner());
addAnimatorRunnerToChain(new SlideInAnimatorRunner());
addAnimatorRunnerToChain(new FadeOutAnimatorRunner().setContinueToNextAnimation(false));
}
@Override
public void cancelAndResetAnimators() {
clearChain(); // Not necessary.
imageFirst.animate().cancel();
imageSecond.animate().cancel();
imageThird.animate().cancel();
prepFadeInAnimation(imageSecond);
prepSlideAnimation(imageThird);
}
private void prepSlideAnimation(View view) {
view.animate().cancel();
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null) {
int newX = parent.getWidth() + parent.getPaddingRight();
view.setX(newX);
view.setAlpha(1); // Reset Alpha if we had faded this view out.
}
}
private static void prepFadeInAnimation(View view) {
view.animate().cancel();
view.setAlpha(0);
}
//////////////////
// Runner Classes
//////////////////
private static final long START_DELAY_CLICK_DOWN = 1000;
private static final long START_DELAY_CLICK_UP = 600;
private static final long START_DELAY_SLIDE_IN = 300;
private static final long START_DELAY_FADE_OUT = 3000;
private class ClickDownAnimatorRunner extends AnimatorRunner {
@Override
protected ViewPropertyAnimator buildAnimator() {
imageSecond.animate().cancel();
return imageSecond.animate()
.alpha(1F)
.setStartDelay(START_DELAY_CLICK_DOWN);
}
}
private class ClickUpAnimatorRunner extends AnimatorRunner {
@Override
protected ViewPropertyAnimator buildAnimator() {
imageSecond.animate().cancel();
return imageSecond.animate()
.alpha(0F)
.setStartDelay(START_DELAY_CLICK_UP);
}
}
private class SlideInAnimatorRunner extends AnimatorRunner {
@Override
protected ViewPropertyAnimator buildAnimator() {
prepSlideAnimation(imageThird);
return imageThird.animate()
.translationX(0F)
.setStartDelay(START_DELAY_SLIDE_IN);
}
}
private class FadeOutAnimatorRunner extends AnimatorRunner {
@Override
protected ViewPropertyAnimator buildAnimator() {
imageThird.animate().cancel();
return imageThird.animate()
.alpha(0F)
.setStartDelay(START_DELAY_FADE_OUT);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment