Last active
August 29, 2015 14:06
-
-
Save MinceMan/3664cb880548690b04a2 to your computer and use it in GitHub Desktop.
AnimatorChainer with Examples, Android
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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