514 lines
17 KiB
Java
514 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.graphics.animation;
|
|
|
|
import android.animation.Animator;
|
|
import android.animation.TimeInterpolator;
|
|
import android.animation.ValueAnimator;
|
|
import android.graphics.CanvasProperty;
|
|
import android.graphics.Paint;
|
|
import android.graphics.RecordingCanvas;
|
|
import android.graphics.RenderNode;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.view.Choreographer;
|
|
|
|
import com.android.internal.util.VirtualRefBasePtr;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public class RenderNodeAnimator extends Animator {
|
|
// Keep in sync with enum RenderProperty in Animator.h
|
|
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;
|
|
// The last value in the enum, used for array size initialization
|
|
public static final int LAST_VALUE = ALPHA;
|
|
|
|
// Keep in sync with enum PaintFields in Animator.h
|
|
public static final int PAINT_STROKE_WIDTH = 0;
|
|
|
|
/**
|
|
* Field for the Paint alpha channel, which should be specified as a value
|
|
* between 0 and 255.
|
|
*/
|
|
public static final int PAINT_ALPHA = 1;
|
|
|
|
private VirtualRefBasePtr mNativePtr;
|
|
|
|
private Handler mHandler;
|
|
private RenderNode mTarget;
|
|
private ViewListener mViewListener;
|
|
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;
|
|
// If this is true, we will run any start delays on the UI thread. This is
|
|
// the safe default, and is necessary to ensure start listeners fire at
|
|
// the correct time. Animators created by RippleDrawable (the
|
|
// CanvasProperty<> ones) do not have this expectation, and as such will
|
|
// set this to false so that the renderthread handles the startdelay instead
|
|
private final boolean mUiThreadHandlesDelay;
|
|
private long mStartDelay = 0;
|
|
private long mStartTime;
|
|
|
|
/**
|
|
* Interface used by the view system to update the view hierarchy in conjunction
|
|
* with this animator.
|
|
*/
|
|
public interface ViewListener {
|
|
/** notify the listener that an alpha animation has begun. */
|
|
void onAlphaAnimationStart(float finalAlpha);
|
|
/** notify the listener that the animator has mutated a value that requires invalidation */
|
|
void invalidateParent(boolean forceRedraw);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Creates a new render node animator for a field on a Paint property.
|
|
*
|
|
* @param property The paint property to target
|
|
* @param paintField Paint field to animate, one of {@link #PAINT_ALPHA} or
|
|
* {@link #PAINT_STROKE_WIDTH}
|
|
* @param finalValue The target value for the property
|
|
*/
|
|
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 = ((NativeInterpolator) 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;
|
|
if (mHandler == null) {
|
|
mHandler = new Handler(true);
|
|
}
|
|
applyInterpolator();
|
|
|
|
if (mNativePtr == null) {
|
|
// It's dead, immediately cancel
|
|
cancel();
|
|
} else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
|
|
nSetStartDelay(mNativePtr.get(), mStartDelay);
|
|
doStart();
|
|
} else {
|
|
getHelper().addDelayedAnimation(this);
|
|
}
|
|
}
|
|
|
|
private void doStart() {
|
|
// Alpha is a special snowflake that has the canonical value stored
|
|
// in mTransformationInfo instead of in RenderNode, so we need to update
|
|
// it with the final value here.
|
|
if (mRenderProperty == RenderNodeAnimator.ALPHA && mViewListener != null) {
|
|
mViewListener.onAlphaAnimationStart(mFinalValue);
|
|
}
|
|
|
|
moveToRunningState();
|
|
|
|
if (mViewListener != null) {
|
|
// Kick off a frame to start the process
|
|
mViewListener.invalidateParent(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 (mViewListener != null) {
|
|
// Kick off a frame to flush the state change
|
|
mViewListener.invalidateParent(false);
|
|
}
|
|
} else {
|
|
// It's already dead, jump to onFinish
|
|
onFinished();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void pause() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public void resume() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
/** @hide */
|
|
public void setViewListener(ViewListener listener) {
|
|
mViewListener = listener;
|
|
}
|
|
|
|
/** Sets the animation target to the owning view of the RecordingCanvas */
|
|
public final void setTarget(RecordingCanvas canvas) {
|
|
setTarget(canvas.mNode);
|
|
}
|
|
|
|
/** Sets the node that is to be the target of this animation */
|
|
protected void setTarget(RenderNode node) {
|
|
checkMutable();
|
|
if (mTarget != null) {
|
|
throw new IllegalStateException("Target already set!");
|
|
}
|
|
nSetListener(mNativePtr.get(), this);
|
|
mTarget = node;
|
|
mTarget.addAnimator(this);
|
|
}
|
|
|
|
/** Set the start value for the animation */
|
|
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) {
|
|
// Unlikely but possible, the native side has been destroyed
|
|
// before we have started.
|
|
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);
|
|
}
|
|
|
|
// Release the native object, as it has a global reference to us. This
|
|
// breaks the cyclic reference chain, and allows this object to be
|
|
// GC'd
|
|
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;
|
|
}
|
|
|
|
public long getNativeAnimator() {
|
|
return mNativePtr.get();
|
|
}
|
|
|
|
/**
|
|
* @return true if the animator was started, false if still delayed
|
|
*/
|
|
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;
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Called by native
|
|
private static void callOnFinished(RenderNodeAnimator animator) {
|
|
if (animator.mHandler != null) {
|
|
animator.mHandler.post(animator::onFinished);
|
|
} else {
|
|
new Handler(Looper.getMainLooper(), null, true).post(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);
|
|
}
|