mTargetNameMap;
public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy,
Callback owner, Resources res) {
if (copy != null) {
mChangingConfigurations = copy.mChangingConfigurations;
if (copy.mVectorDrawable != null) {
final ConstantState cs = copy.mVectorDrawable.getConstantState();
if (res != null) {
mVectorDrawable = (VectorDrawable) cs.newDrawable(res);
} else {
mVectorDrawable = (VectorDrawable) cs.newDrawable();
}
mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate();
mVectorDrawable.setCallback(owner);
mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection());
mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
mVectorDrawable.setAllowCaching(false);
}
if (copy.mAnimators != null) {
mAnimators = new ArrayList<>(copy.mAnimators);
}
if (copy.mTargetNameMap != null) {
mTargetNameMap = new ArrayMap<>(copy.mTargetNameMap);
}
if (copy.mPendingAnims != null) {
mPendingAnims = new ArrayList<>(copy.mPendingAnims);
}
} else {
mVectorDrawable = new VectorDrawable();
}
}
@Override
public boolean canApplyTheme() {
return (mVectorDrawable != null && mVectorDrawable.canApplyTheme())
|| mPendingAnims != null || super.canApplyTheme();
}
@Override
public Drawable newDrawable() {
return new AnimatedVectorDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
return new AnimatedVectorDrawable(this, res);
}
@Override
public @Config int getChangingConfigurations() {
return mChangingConfigurations;
}
public void addPendingAnimator(int resId, float pathErrorScale, String target) {
if (mPendingAnims == null) {
mPendingAnims = new ArrayList<>(1);
}
mPendingAnims.add(new PendingAnimator(resId, pathErrorScale, target));
}
public void addTargetAnimator(String targetName, Animator animator) {
if (mAnimators == null) {
mAnimators = new ArrayList<>(1);
mTargetNameMap = new ArrayMap<>(1);
}
mAnimators.add(animator);
mTargetNameMap.put(animator, targetName);
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.v(LOGTAG, "add animator for target " + targetName + " " + animator);
}
}
/**
* Prepares a local set of mutable animators based on the constant
* state.
*
* If there are any pending uninflated animators, attempts to inflate
* them immediately against the provided resources object.
*
* @param animatorSet the animator set to which the animators should
* be added
* @param res the resources against which to inflate any pending
* animators, or {@code null} if not available
*/
public void prepareLocalAnimators(@NonNull AnimatorSet animatorSet,
@Nullable Resources res) {
// Check for uninflated animators. We can remove this after we add
// support for Animator.applyTheme(). See comments in inflate().
if (mPendingAnims != null) {
// Attempt to load animators without applying a theme.
if (res != null) {
inflatePendingAnimators(res, null);
} else {
Log.e(LOGTAG, "Failed to load animators. Either the AnimatedVectorDrawable"
+ " must be created using a Resources object or applyTheme() must be"
+ " called with a non-null Theme object.");
}
mPendingAnims = null;
}
// Perform a deep copy of the constant state's animators.
final int count = mAnimators == null ? 0 : mAnimators.size();
if (count > 0) {
final Animator firstAnim = prepareLocalAnimator(0);
final AnimatorSet.Builder builder = animatorSet.play(firstAnim);
for (int i = 1; i < count; ++i) {
final Animator nextAnim = prepareLocalAnimator(i);
builder.with(nextAnim);
}
}
}
/**
* Prepares a local animator for the given index within the constant
* state's list of animators.
*
* @param index the index of the animator within the constant state
*/
private Animator prepareLocalAnimator(int index) {
final Animator animator = mAnimators.get(index);
final Animator localAnimator = animator.clone();
final String targetName = mTargetNameMap.get(animator);
final Object target = mVectorDrawable.getTargetByName(targetName);
localAnimator.setTarget(target);
return localAnimator;
}
/**
* Inflates pending animators, if any, against a theme. Clears the list of
* pending animators.
*
* @param t the theme against which to inflate the animators
*/
public void inflatePendingAnimators(@NonNull Resources res, @Nullable Theme t) {
final ArrayList pendingAnims = mPendingAnims;
if (pendingAnims != null) {
mPendingAnims = null;
for (int i = 0, count = pendingAnims.size(); i < count; i++) {
final PendingAnimator pendingAnimator = pendingAnims.get(i);
final Animator objectAnimator = pendingAnimator.newInstance(res, t);
addTargetAnimator(pendingAnimator.target, objectAnimator);
}
}
}
/**
* Basically a constant state for Animators until we actually implement
* constant states for Animators.
*/
private static class PendingAnimator {
public final int animResId;
public final float pathErrorScale;
public final String target;
public PendingAnimator(int animResId, float pathErrorScale, String target) {
this.animResId = animResId;
this.pathErrorScale = pathErrorScale;
this.target = target;
}
public Animator newInstance(Resources res, Theme theme) {
return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale);
}
}
}
@Override
public boolean isRunning() {
return mAnimatorSet.isRunning();
}
/**
* Resets the AnimatedVectorDrawable to the start state as specified in the animators.
*/
public void reset() {
ensureAnimatorSet();
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.w(LOGTAG, "calling reset on AVD: " +
((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
getConstantState()).mVectorDrawable.getConstantState()).mRootName
+ ", at: " + this);
}
mAnimatorSet.reset();
}
@Override
public void start() {
ensureAnimatorSet();
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.w(LOGTAG, "calling start on AVD: " +
((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
getConstantState()).mVectorDrawable.getConstantState()).mRootName
+ ", at: " + this);
}
mAnimatorSet.start();
}
@NonNull
private void ensureAnimatorSet() {
if (mAnimatorSetFromXml == null) {
// TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly
// with a list of LocalAnimators.
mAnimatorSetFromXml = new AnimatorSet();
mAnimatedVectorState.prepareLocalAnimators(mAnimatorSetFromXml, mRes);
mAnimatorSet.init(mAnimatorSetFromXml);
mRes = null;
}
}
@Override
public void stop() {
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.w(LOGTAG, "calling stop on AVD: " +
((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
getConstantState()).mVectorDrawable.getConstantState())
.mRootName + ", at: " + this);
}
mAnimatorSet.end();
}
/**
* Reverses ongoing animations or starts pending animations in reverse.
*
* NOTE: Only works if all animations support reverse. Otherwise, this will
* do nothing.
* @hide
*/
public void reverse() {
ensureAnimatorSet();
// Only reverse when all the animators can be reversed.
if (!canReverse()) {
Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
return;
}
mAnimatorSet.reverse();
}
/**
* @hide
*/
public boolean canReverse() {
return mAnimatorSet.canReverse();
}
private final Callback mCallback = new Callback() {
@Override
public void invalidateDrawable(@NonNull Drawable who) {
invalidateSelf();
}
@Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
unscheduleSelf(what);
}
};
@Override
public void registerAnimationCallback(@NonNull AnimationCallback callback) {
if (callback == null) {
return;
}
// Add listener accordingly.
if (mAnimationCallbacks == null) {
mAnimationCallbacks = new ArrayList<>();
}
mAnimationCallbacks.add(callback);
if (mAnimatorListener == null) {
// Create a animator listener and trigger the callback events when listener is
// triggered.
mAnimatorListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
ArrayList tmpCallbacks = new ArrayList<>(mAnimationCallbacks);
int size = tmpCallbacks.size();
for (int i = 0; i < size; i ++) {
tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawable.this);
}
}
@Override
public void onAnimationEnd(Animator animation) {
ArrayList tmpCallbacks = new ArrayList<>(mAnimationCallbacks);
int size = tmpCallbacks.size();
for (int i = 0; i < size; i ++) {
tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawable.this);
}
}
};
}
mAnimatorSet.setListener(mAnimatorListener);
}
// A helper function to clean up the animator listener in the mAnimatorSet.
private void removeAnimatorSetListener() {
if (mAnimatorListener != null) {
mAnimatorSet.removeListener(mAnimatorListener);
mAnimatorListener = null;
}
}
@Override
public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
if (mAnimationCallbacks == null || callback == null) {
// Nothing to be removed.
return false;
}
boolean removed = mAnimationCallbacks.remove(callback);
// When the last call back unregistered, remove the listener accordingly.
if (mAnimationCallbacks.size() == 0) {
removeAnimatorSetListener();
}
return removed;
}
@Override
public void clearAnimationCallbacks() {
removeAnimatorSetListener();
if (mAnimationCallbacks == null) {
return;
}
mAnimationCallbacks.clear();
}
private interface VectorDrawableAnimator {
void init(@NonNull AnimatorSet set);
void start();
void end();
void reset();
void reverse();
boolean canReverse();
void setListener(AnimatorListener listener);
void removeListener(AnimatorListener listener);
void onDraw(Canvas canvas);
boolean isStarted();
boolean isRunning();
boolean isInfinite();
void pause();
void resume();
}
private static class VectorDrawableAnimatorUI implements VectorDrawableAnimator {
// mSet is only initialized in init(). So we need to check whether it is null before any
// operation.
private AnimatorSet mSet = null;
private final Drawable mDrawable;
// Caching the listener in the case when listener operation is called before the mSet is
// setup by init().
private ArrayList mListenerArray = null;
private boolean mIsInfinite = false;
VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) {
mDrawable = drawable;
}
@Override
public void init(@NonNull AnimatorSet set) {
if (mSet != null) {
// Already initialized
throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " +
"re-initialized");
}
// Keep a deep copy of the set, such that set can be still be constantly representing
// the static content from XML file.
mSet = set.clone();
mIsInfinite = mSet.getTotalDuration() == Animator.DURATION_INFINITE;
// If there are listeners added before calling init(), now they should be setup.
if (mListenerArray != null && !mListenerArray.isEmpty()) {
for (int i = 0; i < mListenerArray.size(); i++) {
mSet.addListener(mListenerArray.get(i));
}
mListenerArray.clear();
mListenerArray = null;
}
}
// Although start(), reset() and reverse() should call init() already, it is better to
// protect these functions from NPE in any situation.
@Override
public void start() {
if (mSet == null || mSet.isStarted()) {
return;
}
mSet.start();
invalidateOwningView();
}
@Override
public void end() {
if (mSet == null) {
return;
}
mSet.end();
}
@Override
public void reset() {
if (mSet == null) {
return;
}
start();
mSet.cancel();
}
@Override
public void reverse() {
if (mSet == null) {
return;
}
mSet.reverse();
invalidateOwningView();
}
@Override
public boolean canReverse() {
return mSet != null && mSet.canReverse();
}
@Override
public void setListener(AnimatorListener listener) {
if (mSet == null) {
if (mListenerArray == null) {
mListenerArray = new ArrayList();
}
mListenerArray.add(listener);
} else {
mSet.addListener(listener);
}
}
@Override
public void removeListener(AnimatorListener listener) {
if (mSet == null) {
if (mListenerArray == null) {
return;
}
mListenerArray.remove(listener);
} else {
mSet.removeListener(listener);
}
}
@Override
public void onDraw(Canvas canvas) {
if (mSet != null && mSet.isStarted()) {
invalidateOwningView();
}
}
@Override
public boolean isStarted() {
return mSet != null && mSet.isStarted();
}
@Override
public boolean isRunning() {
return mSet != null && mSet.isRunning();
}
@Override
public boolean isInfinite() {
return mIsInfinite;
}
@Override
public void pause() {
if (mSet == null) {
return;
}
mSet.pause();
}
@Override
public void resume() {
if (mSet == null) {
return;
}
mSet.resume();
}
private void invalidateOwningView() {
mDrawable.invalidateSelf();
}
}
/**
* @hide
*/
public static class VectorDrawableAnimatorRT implements VectorDrawableAnimator {
private static final int START_ANIMATION = 1;
private static final int REVERSE_ANIMATION = 2;
private static final int RESET_ANIMATION = 3;
private static final int END_ANIMATION = 4;
private AnimatorListener mListener = null;
private final LongArray mStartDelays = new LongArray();
private PropertyValuesHolder.PropertyValues mTmpValues =
new PropertyValuesHolder.PropertyValues();
private long mSetPtr = 0;
private boolean mContainsSequentialAnimators = false;
private boolean mStarted = false;
private boolean mInitialized = false;
private boolean mIsReversible = false;
private boolean mIsInfinite = false;
// This needs to be set before parsing starts.
private boolean mShouldIgnoreInvalidAnim;
// TODO: Consider using NativeAllocationRegistery to track native allocation
private final VirtualRefBasePtr mSetRefBasePtr;
private WeakReference mLastSeenTarget = null;
private int mLastListenerId = 0;
private final IntArray mPendingAnimationActions = new IntArray();
private final Drawable mDrawable;
VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) {
mDrawable = drawable;
mSetPtr = nCreateAnimatorSet();
// Increment ref count on native AnimatorSet, so it doesn't get released before Java
// side is done using it.
mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr);
}
@Override
public void init(@NonNull AnimatorSet set) {
if (mInitialized) {
// Already initialized
throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " +
"re-initialized");
}
mShouldIgnoreInvalidAnim = shouldIgnoreInvalidAnimation();
parseAnimatorSet(set, 0);
mInitialized = true;
mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE;
// Check reversible.
mIsReversible = true;
if (mContainsSequentialAnimators) {
mIsReversible = false;
} else {
// Check if there's any start delay set on child
for (int i = 0; i < mStartDelays.size(); i++) {
if (mStartDelays.get(i) > 0) {
mIsReversible = false;
return;
}
}
}
}
private void parseAnimatorSet(AnimatorSet set, long startTime) {
ArrayList animators = set.getChildAnimations();
boolean playTogether = set.shouldPlayTogether();
// Convert AnimatorSet to VectorDrawableAnimatorRT
for (int i = 0; i < animators.size(); i++) {
Animator animator = animators.get(i);
// Here we only support ObjectAnimator
if (animator instanceof AnimatorSet) {
parseAnimatorSet((AnimatorSet) animator, startTime);
} else if (animator instanceof ObjectAnimator) {
createRTAnimator((ObjectAnimator) animator, startTime);
} // ignore ValueAnimators and others because they don't directly modify VD
// therefore will be useless to AVD.
if (!playTogether) {
// Assume not play together means play sequentially
startTime += animator.getTotalDuration();
mContainsSequentialAnimators = true;
}
}
}
// TODO: This method reads animation data from already parsed Animators. We need to move
// this step further up the chain in the parser to avoid the detour.
private void createRTAnimator(ObjectAnimator animator, long startTime) {
PropertyValuesHolder[] values = animator.getValues();
Object target = animator.getTarget();
if (target instanceof VectorDrawable.VGroup) {
createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target,
startTime);
} else if (target instanceof VectorDrawable.VPath) {
for (int i = 0; i < values.length; i++) {
values[i].getPropertyValues(mTmpValues);
if (mTmpValues.endValue instanceof PathParser.PathData &&
mTmpValues.propertyName.equals("pathData")) {
createRTAnimatorForPath(animator, (VectorDrawable.VPath) target,
startTime);
} else if (target instanceof VectorDrawable.VFullPath) {
createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target,
startTime);
} else if (!mShouldIgnoreInvalidAnim) {
throw new IllegalArgumentException("ClipPath only supports PathData " +
"property");
}
}
} else if (target instanceof VectorDrawable.VectorDrawableState) {
createRTAnimatorForRootGroup(values, animator,
(VectorDrawable.VectorDrawableState) target, startTime);
} else if (!mShouldIgnoreInvalidAnim) {
// Should never get here
throw new UnsupportedOperationException("Target should be either VGroup, VPath, " +
"or ConstantState, " + target == null ? "Null target" : target.getClass() +
" is not supported");
}
}
private void createRTAnimatorForGroup(PropertyValuesHolder[] values,
ObjectAnimator animator, VectorDrawable.VGroup target,
long startTime) {
long nativePtr = target.getNativePtr();
int propertyId;
for (int i = 0; i < values.length; i++) {
// TODO: We need to support the rare case in AVD where no start value is provided
values[i].getPropertyValues(mTmpValues);
propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName);
if (mTmpValues.type != Float.class && mTmpValues.type != float.class) {
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.e(LOGTAG, "Unsupported type: " +
mTmpValues.type + ". Only float value is supported for Groups.");
}
continue;
}
if (propertyId < 0) {
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.e(LOGTAG, "Unsupported property: " +
mTmpValues.propertyName + " for Vector Drawable Group");
}
continue;
}
long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId,
(Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
if (mTmpValues.dataSource != null) {
float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
.getDuration());
nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
}
createNativeChildAnimator(propertyPtr, startTime, animator);
}
}
private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target,
long startTime) {
long nativePtr = target.getNativePtr();
long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue)
.getNativePtr();
long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue)
.getNativePtr();
long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr,
endPathDataPtr);
createNativeChildAnimator(propertyPtr, startTime, animator);
}
private void createRTAnimatorForFullPath(ObjectAnimator animator,
VectorDrawable.VFullPath target, long startTime) {
int propertyId = target.getPropertyIndex(mTmpValues.propertyName);
long propertyPtr;
long nativePtr = target.getNativePtr();
if (mTmpValues.type == Float.class || mTmpValues.type == float.class) {
if (propertyId < 0) {
if (mShouldIgnoreInvalidAnim) {
return;
} else {
throw new IllegalArgumentException("Property: " + mTmpValues.propertyName
+ " is not supported for FullPath");
}
}
propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId,
(Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
} else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) {
propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId,
(Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue);
} else {
if (mShouldIgnoreInvalidAnim) {
return;
} else {
throw new UnsupportedOperationException("Unsupported type: " +
mTmpValues.type + ". Only float, int or PathData value is " +
"supported for Paths.");
}
}
if (mTmpValues.dataSource != null) {
float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
.getDuration());
nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
}
createNativeChildAnimator(propertyPtr, startTime, animator);
}
private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values,
ObjectAnimator animator, VectorDrawable.VectorDrawableState target,
long startTime) {
long nativePtr = target.getNativeRenderer();
if (!animator.getPropertyName().equals("alpha")) {
if (mShouldIgnoreInvalidAnim) {
return;
} else {
throw new UnsupportedOperationException("Only alpha is supported for root "
+ "group");
}
}
Float startValue = null;
Float endValue = null;
for (int i = 0; i < values.length; i++) {
values[i].getPropertyValues(mTmpValues);
if (mTmpValues.propertyName.equals("alpha")) {
startValue = (Float) mTmpValues.startValue;
endValue = (Float) mTmpValues.endValue;
break;
}
}
if (startValue == null && endValue == null) {
if (mShouldIgnoreInvalidAnim) {
return;
} else {
throw new UnsupportedOperationException("No alpha values are specified");
}
}
long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue);
createNativeChildAnimator(propertyPtr, startTime, animator);
}
// These are the data points that define the value of the animating properties.
// e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1]
// a point on the path corresponds to the values of translateX and translateY.
// TODO: (Optimization) We should pass the path down in native and chop it into segments
// in native.
private static float[] createDataPoints(
PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) {
long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
float values[] = new float[numAnimFrames];
float lastFrame = numAnimFrames - 1;
for (int i = 0; i < numAnimFrames; i++) {
float fraction = i / lastFrame;
values[i] = (Float) dataSource.getValueAtFraction(fraction);
}
return values;
}
private void createNativeChildAnimator(long propertyPtr, long extraDelay,
ObjectAnimator animator) {
long duration = animator.getDuration();
int repeatCount = animator.getRepeatCount();
long startDelay = extraDelay + animator.getStartDelay();
TimeInterpolator interpolator = animator.getInterpolator();
long nativeInterpolator =
RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration);
startDelay *= ValueAnimator.getDurationScale();
duration *= ValueAnimator.getDurationScale();
mStartDelays.add(startDelay);
nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration,
repeatCount);
}
/**
* Holds a weak reference to the target that was last seen (through the DisplayListCanvas
* in the last draw call), so that when animator set needs to start, we can add the animator
* to the last seen RenderNode target and start right away.
*/
protected void recordLastSeenTarget(DisplayListCanvas canvas) {
mLastSeenTarget = new WeakReference(
RenderNodeAnimatorSetHelper.getTarget(canvas));
if (mPendingAnimationActions.size() > 0 && useLastSeenTarget()) {
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.d(LOGTAG, "Target is set in the next frame");
}
for (int i = 0; i < mPendingAnimationActions.size(); i++) {
handlePendingAction(mPendingAnimationActions.get(i));
}
mPendingAnimationActions.clear();
}
}
private void handlePendingAction(int pendingAnimationAction) {
if (pendingAnimationAction == START_ANIMATION) {
startAnimation();
} else if (pendingAnimationAction == REVERSE_ANIMATION) {
reverseAnimation();
} else if (pendingAnimationAction == RESET_ANIMATION) {
resetAnimation();
} else if (pendingAnimationAction == END_ANIMATION) {
endAnimation();
} else {
throw new UnsupportedOperationException("Animation action " +
pendingAnimationAction + "is not supported");
}
}
private boolean useLastSeenTarget() {
if (mLastSeenTarget != null) {
final RenderNode target = mLastSeenTarget.get();
if (target != null && target.isAttached()) {
target.addAnimator(this);
return true;
}
}
return false;
}
private void invalidateOwningView() {
mDrawable.invalidateSelf();
}
private void addPendingAction(int pendingAnimationAction) {
invalidateOwningView();
mPendingAnimationActions.add(pendingAnimationAction);
}
@Override
public void start() {
if (!mInitialized) {
return;
}
if (useLastSeenTarget()) {
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
}
startAnimation();
} else {
addPendingAction(START_ANIMATION);
}
}
@Override
public void end() {
if (!mInitialized) {
return;
}
if (useLastSeenTarget()) {
endAnimation();
} else {
addPendingAction(END_ANIMATION);
}
}
@Override
public void reset() {
if (!mInitialized) {
return;
}
if (useLastSeenTarget()) {
resetAnimation();
} else {
addPendingAction(RESET_ANIMATION);
}
}
// Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
// animators or when the animator set has a start delay
@Override
public void reverse() {
if (!mIsReversible || !mInitialized) {
return;
}
if (useLastSeenTarget()) {
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java");
}
reverseAnimation();
} else {
addPendingAction(REVERSE_ANIMATION);
}
}
// This should only be called after animator has been added to the RenderNode target.
private void startAnimation() {
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.w(LOGTAG, "starting animation on VD: " +
((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
mDrawable.getConstantState()).mVectorDrawable.getConstantState())
.mRootName);
}
mStarted = true;
nStart(mSetPtr, this, ++mLastListenerId);
invalidateOwningView();
if (mListener != null) {
mListener.onAnimationStart(null);
}
}
// This should only be called after animator has been added to the RenderNode target.
private void endAnimation() {
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.w(LOGTAG, "ending animation on VD: " +
((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
mDrawable.getConstantState()).mVectorDrawable.getConstantState())
.mRootName);
}
nEnd(mSetPtr);
invalidateOwningView();
}
// This should only be called after animator has been added to the RenderNode target.
private void resetAnimation() {
nReset(mSetPtr);
invalidateOwningView();
}
// This should only be called after animator has been added to the RenderNode target.
private void reverseAnimation() {
mStarted = true;
nReverse(mSetPtr, this, ++mLastListenerId);
invalidateOwningView();
if (mListener != null) {
mListener.onAnimationStart(null);
}
}
public long getAnimatorNativePtr() {
return mSetPtr;
}
@Override
public boolean canReverse() {
return mIsReversible;
}
@Override
public boolean isStarted() {
return mStarted;
}
@Override
public boolean isRunning() {
if (!mInitialized) {
return false;
}
return mStarted;
}
@Override
public void setListener(AnimatorListener listener) {
mListener = listener;
}
@Override
public void removeListener(AnimatorListener listener) {
mListener = null;
}
@Override
public void onDraw(Canvas canvas) {
if (canvas.isHardwareAccelerated()) {
recordLastSeenTarget((DisplayListCanvas) canvas);
}
}
@Override
public boolean isInfinite() {
return mIsInfinite;
}
@Override
public void pause() {
// TODO: Implement pause for Animator On RT.
}
@Override
public void resume() {
// TODO: Implement resume for Animator On RT.
}
private void onAnimationEnd(int listenerId) {
if (listenerId != mLastListenerId) {
return;
}
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.d(LOGTAG, "on finished called from native");
}
mStarted = false;
// Invalidate in the end of the animation to make sure the data in
// RT thread is synced back to UI thread.
invalidateOwningView();
if (mListener != null) {
mListener.onAnimationEnd(null);
}
}
// onFinished: should be called from native
private static void callOnFinished(VectorDrawableAnimatorRT set, int id) {
set.onAnimationEnd(id);
}
}
private static native long nCreateAnimatorSet();
private static native void nAddAnimator(long setPtr, long propertyValuesHolder,
long nativeInterpolator, long startDelay, long duration, int repeatCount);
private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
float startValue, float endValue);
private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
long endValuePtr);
private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
int startValue, int endValue);
private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId,
float startValue, float endValue);
private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
float endValue);
private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
private static native void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
private static native void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
private static native void nEnd(long animatorSetPtr);
private static native void nReset(long animatorSetPtr);
}