Skip to content

Instantly share code, notes, and snippets.

@chRyNaN
Created March 25, 2016 16:18
Show Gist options
  • Save chRyNaN/6343586782910694f498 to your computer and use it in GitHub Desktop.
Save chRyNaN/6343586782910694f498 to your computer and use it in GitHub Desktop.
An Android class that supports animating over an array of values.
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import java.lang.reflect.Field;
/**
* Created by chRyNaN on 3/24/2016. This class encapsulates an AnimatorSet and an ObjectAnimator (actually a ValueAnimator
* but it updates the value of an Object similar to how an ObjectAnimator works) and performs animations over an array of
* values from their starting values to their end values. Uses a form of a Builder/Chain design pattern to make the class
* intuitive to use. Example usage of this class: ArrayObjectAnimator.with(object).over(fieldName).from(array).to(array).start();.
*/
public class ArrayObjectAnimator<S, T> {
private S object;
private T[] startArray;
private T[] endArray;
private T[] middleArray;
private String fieldName;
private AnimatorSet set;
private long duration;
private ValueAnimator.AnimatorUpdateListener updateListener;
private TimeInterpolator interpolator;
private TypeEvaluator<T> evaluator;
/*
* A private constructor to avoid direct instantiation of the object. Instead the object should be used in a chained method
* pattern, for instance, ArrayObjectAnimator.with(object).from(array).to(array);.
*/
private ArrayObjectAnimator(S object, T[] startArray){
this.object = object;
this.startArray = startArray;
this.duration = 500; //Defaults the time spent for the animation to half a second
}
/*
* A private constructor to avoid direct instantiation of the object. Similar to the two parameter constructor
* (S object, T[] startArray) but has a String fieldName parameter for convenience to the user.
*/
private ArrayObjectAnimator(S object, T[] startArray, String fieldName){
this.object = object;
this.startArray = startArray;
this.fieldName = fieldName;
this.duration = 500;
}
/*
* Specifies the array of ending values.
*/
public ArrayObjectAnimator<S, T> to(T[] array){
this.endArray = array;
return this;
}
/*
* Specifies the name of the field belonging to the S object, that will be replaced with the animated values between T startArray
* and T endArray, during the animation.
*/
public ArrayObjectAnimator<S, T> over(String fieldName){
this.fieldName = fieldName;
return this;
}
/*
* Specifies how long the animation should play.
*/
public ArrayObjectAnimator<S, T> duration(long duration){
this.duration = duration;
return this;
}
/*
* Specifies the listener to be alerted on an animation update event.
*/
public ArrayObjectAnimator<S, T> onUpdate(ValueAnimator.AnimatorUpdateListener listener){
this.updateListener = listener;
return this;
}
/*
* Alerts the user specified AnimationUpdateListener that the animation has updated.
*/
private void alertUpdateListener(ValueAnimator anim){
if(updateListener != null){
updateListener.onAnimationUpdate(anim);
}
}
/*
* Updates the underlying object's property value. References the S object field and the String fieldName field.
* This method is used because we specify a ValueAnimator for each item in the array and then update the whole
* array once all items are finished updating. This is essential for it to work because it looks like the only
* alternative is to use the ObjectAnimator's ofMultiFloat() method but that wasn't introduced until API 21.
* So, this is our work around.
*/
private void updateObject(){
try {
if (object != null && fieldName != null) {
Field f = object.getClass().getField(fieldName);
f.setAccessible(true);
f.set(object, middleArray);
}
}catch(Exception e){
e.printStackTrace();
}
}
/*
* Specifies the interpolator to be used for the animation.
*/
public ArrayObjectAnimator<S, T> interpolator(TimeInterpolator interpolator){
this.interpolator = interpolator;
return this;
}
/*
* Specifies the TypeEvaluator to be used if the T type is an Object type other than Float or Integer.
* If this is not set, and the type T is not Float or Integer, then the animation won't be set because
* there's no way of knowing how to handle the Object value.
*/
public ArrayObjectAnimator<S, T> evaluator(TypeEvaluator<T> evaluator){
this.evaluator = evaluator;
return this;
}
/*
* Returns an already started AnimatorSet. A user can further call start on the AnimatorSet with undefined behavior (dependent on
* the behavior of the AnimatorSet class) but it is recommended not to as the AnimatorSet returned will already of been started.
* The AnimatorSet is returned for convenience to the user, for instance, the ability to cancel the animation when desired.
* If the AnimatorSet object is needed without it already being started, use the animator() method instead.
*/
public AnimatorSet start(){
set = animator();
set.start();
return set;
}
/*
* Gets the underlying AnimatorSet object for the animations.
*/
public AnimatorSet animator(){
if(startArray == null || endArray == null){
return null;
}
if(set != null){
set.cancel();
}
set = new AnimatorSet();
ValueAnimator anim = null, prevAnim = null;
if(startArray instanceof Float[]){
middleArray = (T[]) new Float[startArray.length];
for(int i = 0; i < startArray.length; i++){
final int index = i;
anim = ValueAnimator.ofFloat((Float) startArray[i], (Float) endArray[i]);
anim.setDuration(duration);
if(interpolator != null){
anim.setInterpolator(interpolator);
}
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
middleArray[index] = (T) animation.getAnimatedValue();
if (index >= startArray.length - 1) {
//All objects are done updating so we can update the object and alert the
//user specified AnimationUpdateListener.
updateObject();
alertUpdateListener(animation);
}
}
});
if(i != 0){
set.play(anim).before(prevAnim);
}
prevAnim = anim;
}
}else if(startArray instanceof Integer[]){
middleArray = (T[]) new Integer[startArray.length];
for(int i = 0; i < startArray.length; i++){
final int index = i;
anim = ValueAnimator.ofInt((Integer) startArray[i], (Integer) endArray[i]);
anim.setDuration(duration);
if(interpolator != null){
anim.setInterpolator(interpolator);
}
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
middleArray[index] = (T) animation.getAnimatedValue();
if (index >= startArray.length - 1) {
//All objects are done updating so we can update the object and alert the
//user specified AnimationUpdateListener.
updateObject();
alertUpdateListener(animation);
}
}
});
if(i != 0){
set.play(anim).before(prevAnim);
}
prevAnim = anim;
}
}else{
if(evaluator != null) {
middleArray = (T[]) new Object[startArray.length];
for (int i = 0; i < startArray.length; i++) {
final int index = i;
anim = ValueAnimator.ofObject(evaluator, startArray[i], endArray[i]);
anim.setDuration(duration);
if (interpolator != null) {
anim.setInterpolator(interpolator);
}
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
middleArray[index] = (T) animation.getAnimatedValue();
if (index >= startArray.length - 1) {
//All objects are done updating so we can update the object and alert the
//user specified AnimationUpdateListener.
updateObject();
alertUpdateListener(animation);
}
}
});
if (i != 0) {
set.play(anim).before(prevAnim);
}
prevAnim = anim;
}
}
}
return set;
}
/*
* Specifies the object that contains the field that will be animated over. Returns an instance of the SBuilder class storing
* the specified object.
*/
public static final <S> SBuilder<S> with(S object){
return new SBuilder<S>(object);
}
/*
* This class is essential to use the ArrayObjectAnimator class in a builder/chained method design pattern.
* Both types aren't known at one time, so this class is used for one type, S, and contains a single method
* where the second type is specified, T. Once the method from is called, both types are known and an instance
* of the ArrayObjectAnimator class can be created.
*/
public static class SBuilder<S>{
S object;
String fieldName;
public SBuilder(S object){
this.object = object;
}
/*
* Specifies the array of starting values.
*/
public <T> ArrayObjectAnimator<S, T> from(T[] array){
if(fieldName != null){
return new ArrayObjectAnimator<S, T>(object, array, fieldName);
}
return new ArrayObjectAnimator<S, T>(object, array);
}
/*
* Works just like the over method from the ArrayObjectAnimator class. This is a convenience method for the user
* so they can place the over method in either order they choose.
* Ex: 1. ArrayObjectAnimator.with(object).over(field).from(array).to(array).start();
* 2. ArrayObjectAnimator.with(object).from(array).to(array).over(field).start();
*/
public SBuilder<S> over(String fieldName){
this.fieldName = fieldName;
return this;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment