Created
March 25, 2016 16:18
-
-
Save chRyNaN/6343586782910694f498 to your computer and use it in GitHub Desktop.
An Android class that supports animating over an array of values.
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
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