2 * Copyright (C) 2014 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
15 package android.graphics.drawable;
17 import android.animation.Animator;
18 import android.animation.AnimatorInflater;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.AnimatorSet;
21 import android.animation.Animator.AnimatorListener;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.TimeInterpolator;
24 import android.animation.ValueAnimator;
25 import android.animation.ObjectAnimator;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.content.res.ColorStateList;
29 import android.content.res.Resources;
30 import android.content.res.Resources.Theme;
31 import android.content.res.TypedArray;
32 import android.graphics.Canvas;
33 import android.graphics.ColorFilter;
34 import android.graphics.Insets;
35 import android.graphics.Outline;
36 import android.graphics.PorterDuff;
37 import android.graphics.Rect;
38 import android.util.ArrayMap;
39 import android.util.AttributeSet;
40 import android.util.Log;
41 import android.util.LongArray;
42 import android.util.PathParser;
43 import android.util.TimeUtils;
44 import android.view.Choreographer;
45 import android.view.DisplayListCanvas;
46 import android.view.RenderNode;
47 import android.view.RenderNodeAnimatorSetHelper;
48 import android.view.View;
50 import com.android.internal.R;
52 import com.android.internal.util.VirtualRefBasePtr;
53 import org.xmlpull.v1.XmlPullParser;
54 import org.xmlpull.v1.XmlPullParserException;
56 import java.io.IOException;
57 import java.lang.ref.WeakReference;
58 import java.util.ArrayList;
61 * This class uses {@link android.animation.ObjectAnimator} and
62 * {@link android.animation.AnimatorSet} to animate the properties of a
63 * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable.
65 * AnimatedVectorDrawable are normally defined as 3 separate XML files.
68 * First is the XML file for {@link android.graphics.drawable.VectorDrawable}.
69 * Note that we allow the animation to happen on the group's attributes and path's
70 * attributes, which requires they are uniquely named in this XML file. Groups
71 * and paths without animations do not need names.
73 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
75 * <vector xmlns:android="http://schemas.android.com/apk/res/android"
76 * android:height="64dp"
77 * android:width="64dp"
78 * android:viewportHeight="600"
79 * android:viewportWidth="600" >
81 * android:name="rotationGroup"
82 * android:pivotX="300.0"
83 * android:pivotY="300.0"
84 * android:rotation="45.0" >
86 * android:name="v"
87 * android:fillColor="#000000"
88 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
93 * Second is the AnimatedVectorDrawable's XML file, which defines the target
94 * VectorDrawable, the target paths and groups to animate, the properties of the
95 * path and group to animate and the animations defined as the ObjectAnimators
98 * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
99 * Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
101 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
102 * android:drawable="@drawable/vectordrawable" >
104 * android:name="rotationGroup"
105 * android:animation="@anim/rotation" />
107 * android:name="v"
108 * android:animation="@anim/path_morph" />
109 * </animated-vector>
112 * Last is the Animator XML file, which is the same as a normal ObjectAnimator
114 * To complete this example, here are the 2 animator files used in avd.xml:
115 * rotation.xml and path_morph.xml.
117 * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees.
120 * android:duration="6000"
121 * android:propertyName="rotation"
122 * android:valueFrom="0"
123 * android:valueTo="360" />
125 * <li>Here is the path_morph.xml, which will morph the path from one shape to
126 * the other. Note that the paths must be compatible for morphing.
127 * In more details, the paths should have exact same length of commands , and
128 * exact same length of parameters for each commands.
129 * Note that the path strings are better stored in strings.xml for reusing.
131 * <set xmlns:android="http://schemas.android.com/apk/res/android">
133 * android:duration="3000"
134 * android:propertyName="pathData"
135 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z"
136 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z"
137 * android:valueType="pathType"/>
141 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable
142 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name
143 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
145 public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
146 private static final String LOGTAG = "AnimatedVectorDrawable";
148 private static final String ANIMATED_VECTOR = "animated-vector";
149 private static final String TARGET = "target";
151 private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
153 /** Local, mutable animator set. */
154 private final VectorDrawableAnimator mAnimatorSet = new VectorDrawableAnimator();
157 * The resources against which this drawable was created. Used to attempt
158 * to inflate animators if applyTheme() doesn't get called.
160 private Resources mRes;
162 private AnimatedVectorDrawableState mAnimatedVectorState;
164 /** Whether the animator set has been prepared. */
165 private boolean mHasAnimatorSet;
167 private boolean mMutated;
169 /** Use a internal AnimatorListener to support callbacks during animation events. */
170 private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null;
171 private AnimatorListener mAnimatorListener = null;
173 public AnimatedVectorDrawable() {
177 private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) {
178 mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res);
183 public Drawable mutate() {
184 if (!mMutated && super.mutate() == this) {
185 mAnimatedVectorState = new AnimatedVectorDrawableState(
186 mAnimatedVectorState, mCallback, mRes);
195 public void clearMutated() {
196 super.clearMutated();
197 if (mAnimatedVectorState.mVectorDrawable != null) {
198 mAnimatedVectorState.mVectorDrawable.clearMutated();
204 public ConstantState getConstantState() {
205 mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations();
206 return mAnimatedVectorState;
210 public int getChangingConfigurations() {
211 return super.getChangingConfigurations() | mAnimatedVectorState.getChangingConfigurations();
215 public void draw(Canvas canvas) {
216 if (canvas.isHardwareAccelerated()) {
217 mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas);
219 mAnimatedVectorState.mVectorDrawable.draw(canvas);
226 protected void onBoundsChange(Rect bounds) {
227 mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
231 protected boolean onStateChange(int[] state) {
232 return mAnimatedVectorState.mVectorDrawable.setState(state);
236 protected boolean onLevelChange(int level) {
237 return mAnimatedVectorState.mVectorDrawable.setLevel(level);
241 public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
242 return mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection);
246 public int getAlpha() {
247 return mAnimatedVectorState.mVectorDrawable.getAlpha();
251 public void setAlpha(int alpha) {
252 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
256 public void setColorFilter(ColorFilter colorFilter) {
257 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
261 public void setTintList(ColorStateList tint) {
262 mAnimatedVectorState.mVectorDrawable.setTintList(tint);
266 public void setHotspot(float x, float y) {
267 mAnimatedVectorState.mVectorDrawable.setHotspot(x, y);
271 public void setHotspotBounds(int left, int top, int right, int bottom) {
272 mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom);
276 public void setTintMode(PorterDuff.Mode tintMode) {
277 mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
281 public boolean setVisible(boolean visible, boolean restart) {
282 mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
283 return super.setVisible(visible, restart);
287 public boolean isStateful() {
288 return mAnimatedVectorState.mVectorDrawable.isStateful();
292 public int getOpacity() {
293 return mAnimatedVectorState.mVectorDrawable.getOpacity();
297 public int getIntrinsicWidth() {
298 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
302 public int getIntrinsicHeight() {
303 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
307 public void getOutline(@NonNull Outline outline) {
308 mAnimatedVectorState.mVectorDrawable.getOutline(outline);
313 public Insets getOpticalInsets() {
314 return mAnimatedVectorState.mVectorDrawable.getOpticalInsets();
318 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
319 throws XmlPullParserException, IOException {
320 final AnimatedVectorDrawableState state = mAnimatedVectorState;
322 int eventType = parser.getEventType();
323 float pathErrorScale = 1;
324 while (eventType != XmlPullParser.END_DOCUMENT) {
325 if (eventType == XmlPullParser.START_TAG) {
326 final String tagName = parser.getName();
327 if (ANIMATED_VECTOR.equals(tagName)) {
328 final TypedArray a = obtainAttributes(res, theme, attrs,
329 R.styleable.AnimatedVectorDrawable);
330 int drawableRes = a.getResourceId(
331 R.styleable.AnimatedVectorDrawable_drawable, 0);
332 if (drawableRes != 0) {
333 VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable(
334 drawableRes, theme).mutate();
335 vectorDrawable.setAllowCaching(false);
336 vectorDrawable.setCallback(mCallback);
337 pathErrorScale = vectorDrawable.getPixelSize();
338 if (state.mVectorDrawable != null) {
339 state.mVectorDrawable.setCallback(null);
341 state.mVectorDrawable = vectorDrawable;
344 } else if (TARGET.equals(tagName)) {
345 final TypedArray a = obtainAttributes(res, theme, attrs,
346 R.styleable.AnimatedVectorDrawableTarget);
347 final String target = a.getString(
348 R.styleable.AnimatedVectorDrawableTarget_name);
349 final int animResId = a.getResourceId(
350 R.styleable.AnimatedVectorDrawableTarget_animation, 0);
351 if (animResId != 0) {
353 final Animator objectAnimator = AnimatorInflater.loadAnimator(
354 res, theme, animResId, pathErrorScale);
355 state.addTargetAnimator(target, objectAnimator);
357 // The animation may be theme-dependent. As a
358 // workaround until Animator has full support for
359 // applyTheme(), postpone loading the animator
360 // until we have a theme in applyTheme().
361 state.addPendingAnimator(animResId, pathErrorScale, target);
369 eventType = parser.next();
372 // If we don't have any pending animations, we don't need to hold a
373 // reference to the resources.
374 mRes = state.mPendingAnims == null ? null : res;
378 public boolean canApplyTheme() {
379 return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme())
380 || super.canApplyTheme();
384 public void applyTheme(Theme t) {
387 final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable;
388 if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
389 vectorDrawable.applyTheme(t);
393 mAnimatedVectorState.inflatePendingAnimators(t.getResources(), t);
396 // If we don't have any pending animations, we don't need to hold a
397 // reference to the resources.
398 if (mAnimatedVectorState.mPendingAnims == null) {
403 private static class AnimatedVectorDrawableState extends ConstantState {
404 int mChangingConfigurations;
405 VectorDrawable mVectorDrawable;
407 /** Animators that require a theme before inflation. */
408 ArrayList<PendingAnimator> mPendingAnims;
410 /** Fully inflated animators awaiting cloning into an AnimatorSet. */
411 ArrayList<Animator> mAnimators;
413 /** Map of animators to their target object names */
414 ArrayMap<Animator, String> mTargetNameMap;
416 public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy,
417 Callback owner, Resources res) {
419 mChangingConfigurations = copy.mChangingConfigurations;
421 if (copy.mVectorDrawable != null) {
422 final ConstantState cs = copy.mVectorDrawable.getConstantState();
424 mVectorDrawable = (VectorDrawable) cs.newDrawable(res);
426 mVectorDrawable = (VectorDrawable) cs.newDrawable();
428 mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate();
429 mVectorDrawable.setCallback(owner);
430 mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection());
431 mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
432 mVectorDrawable.setAllowCaching(false);
435 if (copy.mAnimators != null) {
436 mAnimators = new ArrayList<>(copy.mAnimators);
439 if (copy.mTargetNameMap != null) {
440 mTargetNameMap = new ArrayMap<>(copy.mTargetNameMap);
443 if (copy.mPendingAnims != null) {
444 mPendingAnims = new ArrayList<>(copy.mPendingAnims);
447 mVectorDrawable = new VectorDrawable();
452 public boolean canApplyTheme() {
453 return (mVectorDrawable != null && mVectorDrawable.canApplyTheme())
454 || mPendingAnims != null || super.canApplyTheme();
458 public Drawable newDrawable() {
459 return new AnimatedVectorDrawable(this, null);
463 public Drawable newDrawable(Resources res) {
464 return new AnimatedVectorDrawable(this, res);
468 public int getChangingConfigurations() {
469 return mChangingConfigurations;
472 public void addPendingAnimator(int resId, float pathErrorScale, String target) {
473 if (mPendingAnims == null) {
474 mPendingAnims = new ArrayList<>(1);
476 mPendingAnims.add(new PendingAnimator(resId, pathErrorScale, target));
479 public void addTargetAnimator(String targetName, Animator animator) {
480 if (mAnimators == null) {
481 mAnimators = new ArrayList<>(1);
482 mTargetNameMap = new ArrayMap<>(1);
484 mAnimators.add(animator);
485 mTargetNameMap.put(animator, targetName);
487 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
488 Log.v(LOGTAG, "add animator for target " + targetName + " " + animator);
493 * Prepares a local set of mutable animators based on the constant
496 * If there are any pending uninflated animators, attempts to inflate
497 * them immediately against the provided resources object.
499 * @param animatorSet the animator set to which the animators should
501 * @param res the resources against which to inflate any pending
502 * animators, or {@code null} if not available
504 public void prepareLocalAnimators(@NonNull AnimatorSet animatorSet,
505 @Nullable Resources res) {
506 // Check for uninflated animators. We can remove this after we add
507 // support for Animator.applyTheme(). See comments in inflate().
508 if (mPendingAnims != null) {
509 // Attempt to load animators without applying a theme.
511 inflatePendingAnimators(res, null);
513 Log.e(LOGTAG, "Failed to load animators. Either the AnimatedVectorDrawable"
514 + " must be created using a Resources object or applyTheme() must be"
515 + " called with a non-null Theme object.");
518 mPendingAnims = null;
521 // Perform a deep copy of the constant state's animators.
522 final int count = mAnimators == null ? 0 : mAnimators.size();
524 final Animator firstAnim = prepareLocalAnimator(0);
525 final AnimatorSet.Builder builder = animatorSet.play(firstAnim);
526 for (int i = 1; i < count; ++i) {
527 final Animator nextAnim = prepareLocalAnimator(i);
528 builder.with(nextAnim);
534 * Prepares a local animator for the given index within the constant
535 * state's list of animators.
537 * @param index the index of the animator within the constant state
539 private Animator prepareLocalAnimator(int index) {
540 final Animator animator = mAnimators.get(index);
541 final Animator localAnimator = animator.clone();
542 final String targetName = mTargetNameMap.get(animator);
543 final Object target = mVectorDrawable.getTargetByName(targetName);
544 localAnimator.setTarget(target);
545 return localAnimator;
549 * Inflates pending animators, if any, against a theme. Clears the list of
552 * @param t the theme against which to inflate the animators
554 public void inflatePendingAnimators(@NonNull Resources res, @Nullable Theme t) {
555 final ArrayList<PendingAnimator> pendingAnims = mPendingAnims;
556 if (pendingAnims != null) {
557 mPendingAnims = null;
559 for (int i = 0, count = pendingAnims.size(); i < count; i++) {
560 final PendingAnimator pendingAnimator = pendingAnims.get(i);
561 final Animator objectAnimator = pendingAnimator.newInstance(res, t);
562 addTargetAnimator(pendingAnimator.target, objectAnimator);
568 * Basically a constant state for Animators until we actually implement
569 * constant states for Animators.
571 private static class PendingAnimator {
572 public final int animResId;
573 public final float pathErrorScale;
574 public final String target;
576 public PendingAnimator(int animResId, float pathErrorScale, String target) {
577 this.animResId = animResId;
578 this.pathErrorScale = pathErrorScale;
579 this.target = target;
582 public Animator newInstance(Resources res, Theme theme) {
583 return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale);
589 public boolean isRunning() {
590 return mAnimatorSet.isRunning();
593 private boolean isStarted() {
594 return mAnimatorSet.isStarted();
598 * Resets the AnimatedVectorDrawable to the start state as specified in the animators.
600 public void reset() {
601 mAnimatorSet.reset();
606 public void start() {
609 // If any one of the animator has not ended, do nothing.
614 mAnimatorSet.start();
619 private void ensureAnimatorSet() {
620 if (!mHasAnimatorSet) {
621 // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly
622 // with a list of LocalAnimators.
623 AnimatorSet set = new AnimatorSet();
624 mAnimatedVectorState.prepareLocalAnimators(set, mRes);
625 mHasAnimatorSet = true;
626 mAnimatorSet.initWithAnimatorSet(set);
637 * Reverses ongoing animations or starts pending animations in reverse.
639 * NOTE: Only works if all animations support reverse. Otherwise, this will
643 public void reverse() {
646 // Only reverse when all the animators can be reversed.
648 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
652 mAnimatorSet.reverse();
659 public boolean canReverse() {
660 return mAnimatorSet.canReverse();
663 private final Callback mCallback = new Callback() {
665 public void invalidateDrawable(Drawable who) {
670 public void scheduleDrawable(Drawable who, Runnable what, long when) {
671 scheduleSelf(what, when);
675 public void unscheduleDrawable(Drawable who, Runnable what) {
676 unscheduleSelf(what);
681 public void registerAnimationCallback(@NonNull AnimationCallback callback) {
682 if (callback == null) {
686 // Add listener accordingly.
687 if (mAnimationCallbacks == null) {
688 mAnimationCallbacks = new ArrayList<>();
691 mAnimationCallbacks.add(callback);
693 if (mAnimatorListener == null) {
694 // Create a animator listener and trigger the callback events when listener is
696 mAnimatorListener = new AnimatorListenerAdapter() {
698 public void onAnimationStart(Animator animation) {
699 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks);
700 int size = tmpCallbacks.size();
701 for (int i = 0; i < size; i ++) {
702 tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawable.this);
707 public void onAnimationEnd(Animator animation) {
708 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks);
709 int size = tmpCallbacks.size();
710 for (int i = 0; i < size; i ++) {
711 tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawable.this);
716 mAnimatorSet.setListener(mAnimatorListener);
719 // A helper function to clean up the animator listener in the mAnimatorSet.
720 private void removeAnimatorSetListener() {
721 if (mAnimatorListener != null) {
722 mAnimatorSet.removeListener();
723 mAnimatorListener = null;
728 public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
729 if (mAnimationCallbacks == null || callback == null) {
730 // Nothing to be removed.
733 boolean removed = mAnimationCallbacks.remove(callback);
735 // When the last call back unregistered, remove the listener accordingly.
736 if (mAnimationCallbacks.size() == 0) {
737 removeAnimatorSetListener();
743 public void clearAnimationCallbacks() {
744 removeAnimatorSetListener();
745 if (mAnimationCallbacks == null) {
749 mAnimationCallbacks.clear();
755 public static class VectorDrawableAnimator {
756 private AnimatorListener mListener = null;
757 private final LongArray mStartDelays = new LongArray();
758 private PropertyValuesHolder.PropertyValues mTmpValues =
759 new PropertyValuesHolder.PropertyValues();
760 private long mSetPtr = 0;
761 private boolean mContainsSequentialAnimators = false;
762 private boolean mStarted = false;
763 private boolean mInitialized = false;
764 private boolean mAnimationPending = false;
765 private boolean mIsReversible = false;
766 // TODO: Consider using NativeAllocationRegistery to track native allocation
767 private final VirtualRefBasePtr mSetRefBasePtr;
768 private WeakReference<RenderNode> mTarget = null;
769 private WeakReference<RenderNode> mLastSeenTarget = null;
772 VectorDrawableAnimator() {
773 mSetPtr = nCreateAnimatorSet();
774 // Increment ref count on native AnimatorSet, so it doesn't get released before Java
775 // side is done using it.
776 mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr);
779 private void initWithAnimatorSet(AnimatorSet set) {
781 // Already initialized
782 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " +
785 parseAnimatorSet(set, 0);
789 if (mContainsSequentialAnimators) {
790 mIsReversible = false;
792 // Check if there's any start delay set on child
793 for (int i = 0; i < mStartDelays.size(); i++) {
794 if (mStartDelays.get(i) > 0) {
795 mIsReversible = false;
800 mIsReversible = true;
803 private void parseAnimatorSet(AnimatorSet set, long startTime) {
804 ArrayList<Animator> animators = set.getChildAnimations();
806 boolean playTogether = set.shouldPlayTogether();
807 // Convert AnimatorSet to VectorDrawableAnimator
808 for (int i = 0; i < animators.size(); i++) {
809 Animator animator = animators.get(i);
810 // Here we only support ObjectAnimator
811 if (animator instanceof AnimatorSet) {
812 parseAnimatorSet((AnimatorSet) animator, startTime);
813 } else if (animator instanceof ObjectAnimator) {
814 createRTAnimator((ObjectAnimator) animator, startTime);
815 } // ignore ValueAnimators and others because they don't directly modify VD
816 // therefore will be useless to AVD.
819 // Assume not play together means play sequentially
820 startTime += animator.getTotalDuration();
821 mContainsSequentialAnimators = true;
826 // TODO: This method reads animation data from already parsed Animators. We need to move
827 // this step further up the chain in the parser to avoid the detour.
828 private void createRTAnimator(ObjectAnimator animator, long startTime) {
829 PropertyValuesHolder[] values = animator.getValues();
830 Object target = animator.getTarget();
831 if (target instanceof VectorDrawable.VGroup) {
832 createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target,
834 } else if (target instanceof VectorDrawable.VPath) {
835 for (int i = 0; i < values.length; i++) {
836 values[i].getPropertyValues(mTmpValues);
837 if (mTmpValues.endValue instanceof PathParser.PathData &&
838 mTmpValues.propertyName.equals("pathData")) {
839 createRTAnimatorForPath(animator, (VectorDrawable.VPath) target,
841 } else if (target instanceof VectorDrawable.VFullPath) {
842 createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target,
845 throw new IllegalArgumentException("ClipPath only supports PathData " +
850 } else if (target instanceof VectorDrawable.VectorDrawableState) {
851 createRTAnimatorForRootGroup(values, animator,
852 (VectorDrawable.VectorDrawableState) target, startTime);
854 // Should never get here
855 throw new UnsupportedOperationException("Target should be either VGroup, VPath, " +
856 "or ConstantState, " + target.getClass() + " is not supported");
860 private void createRTAnimatorForGroup(PropertyValuesHolder[] values,
861 ObjectAnimator animator, VectorDrawable.VGroup target,
864 long nativePtr = target.getNativePtr();
866 for (int i = 0; i < values.length; i++) {
867 // TODO: We need to support the rare case in AVD where no start value is provided
868 values[i].getPropertyValues(mTmpValues);
869 propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName);
870 if (mTmpValues.type != Float.class && mTmpValues.type != float.class) {
871 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
872 Log.e(LOGTAG, "Unsupported type: " +
873 mTmpValues.type + ". Only float value is supported for Groups.");
877 if (propertyId < 0) {
878 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
879 Log.e(LOGTAG, "Unsupported property: " +
880 mTmpValues.propertyName + " for Vector Drawable Group");
884 long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId,
885 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
886 if (mTmpValues.dataSource != null) {
887 float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
889 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
891 createNativeChildAnimator(propertyPtr, startTime, animator);
894 private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target,
897 long nativePtr = target.getNativePtr();
898 long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue)
900 long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue)
902 long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr,
904 createNativeChildAnimator(propertyPtr, startTime, animator);
907 private void createRTAnimatorForFullPath(ObjectAnimator animator,
908 VectorDrawable.VFullPath target, long startTime) {
910 int propertyId = target.getPropertyIndex(mTmpValues.propertyName);
912 long nativePtr = target.getNativePtr();
913 if (mTmpValues.type == Float.class || mTmpValues.type == float.class) {
914 if (propertyId < 0) {
915 throw new IllegalArgumentException("Property: " + mTmpValues
916 .propertyName + " is not supported for FullPath");
918 propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId,
919 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
921 } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) {
922 propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId,
923 (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue);
925 throw new UnsupportedOperationException("Unsupported type: " +
926 mTmpValues.type + ". Only float, int or PathData value is " +
927 "supported for Paths.");
929 if (mTmpValues.dataSource != null) {
930 float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
932 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
934 createNativeChildAnimator(propertyPtr, startTime, animator);
937 private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values,
938 ObjectAnimator animator, VectorDrawable.VectorDrawableState target,
940 long nativePtr = target.getNativeRenderer();
941 if (!animator.getPropertyName().equals("alpha")) {
942 throw new UnsupportedOperationException("Only alpha is supported for root " +
945 Float startValue = null;
946 Float endValue = null;
947 for (int i = 0; i < values.length; i++) {
948 values[i].getPropertyValues(mTmpValues);
949 if (mTmpValues.propertyName.equals("alpha")) {
950 startValue = (Float) mTmpValues.startValue;
951 endValue = (Float) mTmpValues.endValue;
955 if (startValue == null && endValue == null) {
956 throw new UnsupportedOperationException("No alpha values are specified");
958 long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue);
959 createNativeChildAnimator(propertyPtr, startTime, animator);
962 // These are the data points that define the value of the animating properties.
963 // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1]
964 // a point on the path corresponds to the values of translateX and translateY.
965 // TODO: (Optimization) We should pass the path down in native and chop it into segments
967 private static float[] createDataPoints(
968 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) {
969 long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
970 int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
971 int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
972 float values[] = new float[numAnimFrames];
973 float lastFrame = numAnimFrames - 1;
974 for (int i = 0; i < numAnimFrames; i++) {
975 float fraction = i / lastFrame;
976 values[i] = (Float) dataSource.getValueAtFraction(fraction);
981 private void createNativeChildAnimator(long propertyPtr, long extraDelay,
982 ObjectAnimator animator) {
983 long duration = animator.getDuration();
984 int repeatCount = animator.getRepeatCount();
985 long startDelay = extraDelay + animator.getStartDelay();
986 TimeInterpolator interpolator = animator.getInterpolator();
987 long nativeInterpolator =
988 RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration);
990 startDelay *= ValueAnimator.getDurationScale();
991 duration *= ValueAnimator.getDurationScale();
993 mStartDelays.add(startDelay);
994 nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration,
999 * Holds a weak reference to the target that was last seen (through the DisplayListCanvas
1000 * in the last draw call), so that when animator set needs to start, we can add the animator
1001 * to the last seen RenderNode target and start right away.
1003 protected void recordLastSeenTarget(DisplayListCanvas canvas) {
1004 if (mAnimationPending) {
1005 mLastSeenTarget = new WeakReference<RenderNode>(
1006 RenderNodeAnimatorSetHelper.getTarget(canvas));
1007 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1008 Log.d(LOGTAG, "Target is set in the next frame");
1010 mAnimationPending = false;
1013 mLastSeenTarget = new WeakReference<RenderNode>(
1014 RenderNodeAnimatorSetHelper.getTarget(canvas));
1019 private boolean setTarget(RenderNode node) {
1020 if (mTarget != null && mTarget.get() != null) {
1021 // TODO: Maybe we want to support target change.
1022 throw new IllegalStateException("Target already set!");
1025 node.addAnimator(this);
1026 mTarget = new WeakReference<RenderNode>(node);
1030 private boolean useLastSeenTarget() {
1031 if (mLastSeenTarget != null && mLastSeenTarget.get() != null) {
1032 setTarget(mLastSeenTarget.get());
1038 public void start() {
1039 if (!mInitialized) {
1047 if (!useLastSeenTarget()) {
1048 mAnimationPending = true;
1052 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1053 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
1056 nStart(mSetPtr, this);
1057 if (mListener != null) {
1058 mListener.onAnimationStart(null);
1064 if (mInitialized && mStarted) {
1071 if (!mInitialized) {
1074 // TODO: Need to implement reset.
1075 Log.w(LOGTAG, "Reset is yet to be implemented");
1079 // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
1080 // animators or when the animator set has a start delay
1082 if (!mIsReversible) {
1085 // TODO: Need to support reverse (non-public API)
1086 Log.w(LOGTAG, "Reverse is yet to be implemented");
1087 nReverse(mSetPtr, this);
1090 public long getAnimatorNativePtr() {
1094 boolean canReverse() {
1095 return mIsReversible;
1098 boolean isStarted() {
1102 boolean isRunning() {
1103 if (!mInitialized) {
1109 void setListener(AnimatorListener listener) {
1110 mListener = listener;
1113 void removeListener() {
1117 private void onAnimationEnd() {
1119 if (mListener != null) {
1120 mListener.onAnimationEnd(null);
1125 // onFinished: should be called from native
1126 private static void callOnFinished(VectorDrawableAnimator set) {
1127 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1128 Log.d(LOGTAG, "on finished called from native");
1130 set.onAnimationEnd();
1134 private static native long nCreateAnimatorSet();
1135 private static native void nAddAnimator(long setPtr, long propertyValuesHolder,
1136 long nativeInterpolator, long startDelay, long duration, int repeatCount);
1138 private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
1139 float startValue, float endValue);
1141 private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
1143 private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
1144 int startValue, int endValue);
1145 private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId,
1146 float startValue, float endValue);
1147 private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
1149 private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
1150 private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set);
1151 private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set);
1152 private static native void nEnd(long animatorSetPtr);
1153 private static native void nReset(long animatorSetPtr);