package android.view;
import android.animation.Animator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.util.SparseIntArray;
import com.android.internal.util.VirtualRefBasePtr;
import com.android.internal.view.animation.FallbackLUTInterpolator;
import com.android.internal.view.animation.HasNativeInterpolator;
import com.android.internal.view.animation.NativeInterpolatorFactory;
import java.util.ArrayList;
public class RenderNodeAnimator extends Animator {
public static final int TRANSLATION_X = 0;
public static final int TRANSLATION_Y = 1;
public static final int TRANSLATION_Z = 2;
public static final int SCALE_X = 3;
public static final int SCALE_Y = 4;
public static final int ROTATION = 5;
public static final int ROTATION_X = 6;
public static final int ROTATION_Y = 7;
public static final int X = 8;
public static final int Y = 9;
public static final int Z = 10;
public static final int ALPHA = 11;
public static final int LAST_VALUE = ALPHA;
public static final int PAINT_STROKE_WIDTH = 0;
public static final int PAINT_ALPHA = 1;
private static final SparseIntArray sViewPropertyAnimatorMap = new SparseIntArray(15) {{
put(ViewPropertyAnimator.TRANSLATION_X, TRANSLATION_X);
put(ViewPropertyAnimator.TRANSLATION_Y, TRANSLATION_Y);
put(ViewPropertyAnimator.TRANSLATION_Z, TRANSLATION_Z);
put(ViewPropertyAnimator.SCALE_X, SCALE_X);
put(ViewPropertyAnimator.SCALE_Y, SCALE_Y);
put(ViewPropertyAnimator.ROTATION, ROTATION);
put(ViewPropertyAnimator.ROTATION_X, ROTATION_X);
put(ViewPropertyAnimator.ROTATION_Y, ROTATION_Y);
put(ViewPropertyAnimator.X, X);
put(ViewPropertyAnimator.Y, Y);
put(ViewPropertyAnimator.Z, Z);
put(ViewPropertyAnimator.ALPHA, ALPHA);
}};
private VirtualRefBasePtr mNativePtr;
private RenderNode mTarget;
private View mViewTarget;
private int mRenderProperty = -1;
private float mFinalValue;
private TimeInterpolator mInterpolator;
private static final int STATE_PREPARE = 0;
private static final int STATE_DELAYED = 1;
private static final int STATE_RUNNING = 2;
private static final int STATE_FINISHED = 3;
private int mState = STATE_PREPARE;
private long mUnscaledDuration = 300;
private long mUnscaledStartDelay = 0;
private final boolean mUiThreadHandlesDelay;
private long mStartDelay = 0;
private long mStartTime;
public static int mapViewPropertyToRenderProperty(int viewProperty) {
return sViewPropertyAnimatorMap.get(viewProperty);
}
public RenderNodeAnimator(int property, float finalValue) {
mRenderProperty = property;
mFinalValue = finalValue;
mUiThreadHandlesDelay = true;
init(nCreateAnimator(property, finalValue));
}
public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
init(nCreateCanvasPropertyFloatAnimator(
property.getNativeContainer(), finalValue));
mUiThreadHandlesDelay = false;
}
public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
init(nCreateCanvasPropertyPaintAnimator(
property.getNativeContainer(), paintField, finalValue));
mUiThreadHandlesDelay = false;
}
public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
init(nCreateRevealAnimator(x, y, startRadius, endRadius));
mUiThreadHandlesDelay = true;
}
private void init(long ptr) {
mNativePtr = new VirtualRefBasePtr(ptr);
}
private void checkMutable() {
if (mState != STATE_PREPARE) {
throw new IllegalStateException("Animator has already started, cannot change it now!");
}
if (mNativePtr == null) {
throw new IllegalStateException("Animator's target has been destroyed "
+ "(trying to modify an animation after activity destroy?)");
}
}
static boolean isNativeInterpolator(TimeInterpolator interpolator) {
return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
}
private void applyInterpolator() {
if (mInterpolator == null || mNativePtr == null) return;
long ni;
if (isNativeInterpolator(mInterpolator)) {
ni = ((NativeInterpolatorFactory)mInterpolator).createNativeInterpolator();
} else {
long duration = nGetDuration(mNativePtr.get());
ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
}
nSetInterpolator(mNativePtr.get(), ni);
}
@Override
public void start() {
if (mTarget == null) {
throw new IllegalStateException("Missing target!");
}
if (mState != STATE_PREPARE) {
throw new IllegalStateException("Already started!");
}
mState = STATE_DELAYED;
applyInterpolator();
if (mNativePtr == null) {
cancel();
} else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
nSetStartDelay(mNativePtr.get(), mStartDelay);
doStart();
} else {
getHelper().addDelayedAnimation(this);
}
}
private void doStart() {
if (mRenderProperty == RenderNodeAnimator.ALPHA) {
mViewTarget.ensureTransformationInfo();
mViewTarget.mTransformationInfo.mAlpha = mFinalValue;
}
moveToRunningState();
if (mViewTarget != null) {
mViewTarget.invalidateViewProperty(true, false);
}
}
private void moveToRunningState() {
mState = STATE_RUNNING;
if (mNativePtr != null) {
nStart(mNativePtr.get());
}
notifyStartListeners();
}
private void notifyStartListeners() {
final ArrayList<AnimatorListener> listeners = cloneListeners();
final int numListeners = listeners == null ? 0 : listeners.size();
for (int i = 0; i < numListeners; i++) {
listeners.get(i).onAnimationStart(this);
}
}
@Override
public void cancel() {
if (mState != STATE_PREPARE && mState != STATE_FINISHED) {
if (mState == STATE_DELAYED) {
getHelper().removeDelayedAnimation(this);
moveToRunningState();
}
final ArrayList<AnimatorListener> listeners = cloneListeners();
final int numListeners = listeners == null ? 0 : listeners.size();
for (int i = 0; i < numListeners; i++) {
listeners.get(i).onAnimationCancel(this);
}
end();
}
}
@Override
public void end() {
if (mState != STATE_FINISHED) {
if (mState < STATE_RUNNING) {
getHelper().removeDelayedAnimation(this);
doStart();
}
if (mNativePtr != null) {
nEnd(mNativePtr.get());
if (mViewTarget != null) {
mViewTarget.invalidateViewProperty(true, false);
}
} else {
onFinished();
}
}
}
@Override
public void pause() {
throw new UnsupportedOperationException();
}
@Override
public void resume() {
throw new UnsupportedOperationException();
}
public void setTarget(View view) {
mViewTarget = view;
setTarget(mViewTarget.mRenderNode);
}
public void setTarget(DisplayListCanvas canvas) {
setTarget(canvas.mNode);
}
private void setTarget(RenderNode node) {
checkMutable();
if (mTarget != null) {
throw new IllegalStateException("Target already set!");
}
nSetListener(mNativePtr.get(), this);
mTarget = node;
mTarget.addAnimator(this);
}
public void setStartValue(float startValue) {
checkMutable();
nSetStartValue(mNativePtr.get(), startValue);
}
@Override
public void setStartDelay(long startDelay) {
checkMutable();
if (startDelay < 0) {
throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
}
mUnscaledStartDelay = startDelay;
mStartDelay = (long) (ValueAnimator.getDurationScale() * startDelay);
}
@Override
public long getStartDelay() {
return mUnscaledStartDelay;
}
@Override
public RenderNodeAnimator setDuration(long duration) {
checkMutable();
if (duration < 0) {
throw new IllegalArgumentException("duration must be positive; " + duration);
}
mUnscaledDuration = duration;
nSetDuration(mNativePtr.get(), (long) (duration * ValueAnimator.getDurationScale()));
return this;
}
@Override
public long getDuration() {
return mUnscaledDuration;
}
@Override
public long getTotalDuration() {
return mUnscaledDuration + mUnscaledStartDelay;
}
@Override
public boolean isRunning() {
return mState == STATE_DELAYED || mState == STATE_RUNNING;
}
@Override
public boolean isStarted() {
return mState != STATE_PREPARE;
}
@Override
public void setInterpolator(TimeInterpolator interpolator) {
checkMutable();
mInterpolator = interpolator;
}
@Override
public TimeInterpolator getInterpolator() {
return mInterpolator;
}
protected void onFinished() {
if (mState == STATE_PREPARE) {
releaseNativePtr();
return;
}
if (mState == STATE_DELAYED) {
getHelper().removeDelayedAnimation(this);
notifyStartListeners();
}
mState = STATE_FINISHED;
final ArrayList<AnimatorListener> listeners = cloneListeners();
final int numListeners = listeners == null ? 0 : listeners.size();
for (int i = 0; i < numListeners; i++) {
listeners.get(i).onAnimationEnd(this);
}
releaseNativePtr();
}
private void releaseNativePtr() {
if (mNativePtr != null) {
mNativePtr.release();
mNativePtr = null;
}
}
@SuppressWarnings("unchecked")
private ArrayList<AnimatorListener> cloneListeners() {
ArrayList<AnimatorListener> listeners = getListeners();
if (listeners != null) {
listeners = (ArrayList<AnimatorListener>) listeners.clone();
}
return listeners;
}
long getNativeAnimator() {
return mNativePtr.get();
}
private boolean processDelayed(long frameTimeMs) {
if (mStartTime == 0) {
mStartTime = frameTimeMs;
} else if ((frameTimeMs - mStartTime) >= mStartDelay) {
doStart();
return true;
}
return false;
}
private static DelayedAnimationHelper getHelper() {
DelayedAnimationHelper helper = sAnimationHelper.get();
if (helper == null) {
helper = new DelayedAnimationHelper();
sAnimationHelper.set(helper);
}
return helper;
}
private static ThreadLocal<DelayedAnimationHelper> sAnimationHelper =
new ThreadLocal<DelayedAnimationHelper>();
private static class DelayedAnimationHelper implements Runnable {
private ArrayList<RenderNodeAnimator> mDelayedAnims = new ArrayList<RenderNodeAnimator>();
private final Choreographer mChoreographer;
private boolean mCallbackScheduled;
public DelayedAnimationHelper() {
mChoreographer = Choreographer.getInstance();
}
public void addDelayedAnimation(RenderNodeAnimator animator) {
mDelayedAnims.add(animator);
scheduleCallback();
}
public void removeDelayedAnimation(RenderNodeAnimator animator) {
mDelayedAnims.remove(animator);
}
private void scheduleCallback() {
if (!mCallbackScheduled) {
mCallbackScheduled = true;
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
}
}
@Override
public void run() {
long frameTimeMs = mChoreographer.getFrameTime();
mCallbackScheduled = false;
int end = 0;
for (int i = 0; i < mDelayedAnims.size(); i++) {
RenderNodeAnimator animator = mDelayedAnims.get(i);
if (!animator.processDelayed(frameTimeMs)) {
if (end != i) {
mDelayedAnims.set(end, animator);
}
end++;
}
}
while (mDelayedAnims.size() > end) {
mDelayedAnims.remove(mDelayedAnims.size() - 1);
}
if (mDelayedAnims.size() > 0) {
scheduleCallback();
}
}
}
private static void callOnFinished(RenderNodeAnimator animator) {
animator.onFinished();
}
@Override
public Animator clone() {
throw new IllegalStateException("Cannot clone this animator");
}
@Override
public void setAllowRunningAsynchronously(boolean mayRunAsync) {
checkMutable();
nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
}
private static native long nCreateAnimator(int property, float finalValue);
private static native long nCreateCanvasPropertyFloatAnimator(
long canvasProperty, float finalValue);
private static native long nCreateCanvasPropertyPaintAnimator(
long canvasProperty, int paintField, float finalValue);
private static native long nCreateRevealAnimator(
int x, int y, float startRadius, float endRadius);
private static native void nSetStartValue(long nativePtr, float startValue);
private static native void nSetDuration(long nativePtr, long duration);
private static native long nGetDuration(long nativePtr);
private static native void nSetStartDelay(long nativePtr, long startDelay);
private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
private static native void nSetListener(long animPtr, RenderNodeAnimator listener);
private static native void nStart(long animPtr);
private static native void nEnd(long animPtr);
}