AnimatedVectorDrawable.java revision 14aedd1fbf52f1b844064a15d583ccfbda6ce57d
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
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
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
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
12 * the License.
13 */
14
15package android.graphics.drawable;
16
17import android.animation.Animator;
18import android.animation.AnimatorInflater;
19import android.animation.ValueAnimator;
20import android.content.res.Resources;
21import android.content.res.Resources.Theme;
22import android.content.res.TypedArray;
23import android.graphics.Canvas;
24import android.graphics.ColorFilter;
25import android.graphics.Rect;
26import android.util.AttributeSet;
27import android.util.Log;
28
29import com.android.internal.R;
30
31import org.xmlpull.v1.XmlPullParser;
32import org.xmlpull.v1.XmlPullParserException;
33
34import java.io.IOException;
35import java.util.ArrayList;
36
37/**
38 * This class uses {@link android.animation.ObjectAnimator} and
39 * {@link android.animation.AnimatorSet} to animate the properties of a
40 * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable.
41 * <p>
42 * AnimatedVectorDrawable are normally defined as 3 separate XML files.
43 * </p>
44 * <p>
45 * First is the XML file for {@link android.graphics.drawable.VectorDrawable}.
46 * Note that we allow the animation happen on the group's attributes and path's
47 * attributes, which requires they are uniquely named in this xml file. Groups
48 * and paths without animations do not need names.
49 * </p>
50 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
51 * <pre>
52 * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
53 *     android:height=&quot;64dp&quot;
54 *     android:width=&quot;64dp&quot;
55 *     android:viewportHeight=&quot;600&quot;
56 *     android:viewportWidth=&quot;600&quot; &gt;
57 *     &lt;group
58 *         android:name=&quot;rotationGroup&quot;
59 *         android:pivotX=&quot;300.0&quot;
60 *         android:pivotY=&quot;300.0&quot;
61 *         android:rotation=&quot;45.0&quot; &gt;
62 *         &lt;path
63 *             android:name=&quot;v&quot;
64 *             android:fillColor=&quot;#000000&quot;
65 *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
66 *     &lt;/group&gt;
67 * &lt;/vector&gt;
68 * </pre></li>
69 * <p>
70 * Second is the AnimatedVectorDrawable's xml file, which defines the target
71 * VectorDrawable, the target paths and groups to animate, the properties of the
72 * path and group to animate and the animations defined as the ObjectAnimators
73 * or AnimatorSets.
74 * </p>
75 * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
76 * Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
77 * <pre>
78 * &lt;animated-vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
79 *   android:drawable=&quot;@drawable/vectordrawable&quot; &gt;
80 *     &lt;target
81 *         android:name=&quot;rotationGroup&quot;
82 *         android:animation=&quot;@anim/rotation&quot; /&gt;
83 *     &lt;target
84 *         android:name=&quot;v&quot;
85 *         android:animation=&quot;@anim/path_morph&quot; /&gt;
86 * &lt;/animated-vector&gt;
87 * </pre></li>
88 * <p>
89 * Last is the Animator xml file, which is the same as a normal ObjectAnimator
90 * or AnimatorSet.
91 * To complete this example, here are the 2 animator files used in avd.xml:
92 * rotation.xml and path_morph.xml.
93 * </p>
94 * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees.
95 * <pre>
96 * &lt;objectAnimator
97 *     android:duration=&quot;6000&quot;
98 *     android:propertyName=&quot;rotation&quot;
99 *     android:valueFrom=&quot;0&quot;
100 *     android:valueTo=&quot;360&quot; /&gt;
101 * </pre></li>
102 * <li>Here is the path_morph.xml, which will morph the path from one shape to
103 * the other. Note that the paths must be compatible for morphing.
104 * In more details, the paths should have exact same length of commands , and
105 * exact same length of parameters for each commands.
106 * Note that the path string are better stored in strings.xml for reusing.
107 * <pre>
108 * &lt;set xmlns:android=&quot;http://schemas.android.com/apk/res/android">;
109 *     &lt;objectAnimator
110 *         android:duration=&quot;3000&quot;
111 *         android:propertyName=&quot;pathData&quot;
112 *         android:valueFrom=&quot;M300,70 l 0,-70 70,70 0,0   -70,70z&quot;
113 *         android:valueTo=&quot;M300,70 l 0,-70 70,0  0,140 -70,0 z&quot;
114 *         android:valueType=&quot;pathType&quot;/&gt;
115 * &lt;/set&gt;
116 * </pre></li>
117 *
118 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable
119 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name
120 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
121 */
122public class AnimatedVectorDrawable extends Drawable implements Animatable {
123    private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName();
124
125    private static final String ANIMATED_VECTOR = "animated-vector";
126    private static final String TARGET = "target";
127
128    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
129
130    private final AnimatedVectorDrawableState mAnimatedVectorState;
131
132
133    public AnimatedVectorDrawable() {
134        mAnimatedVectorState = new AnimatedVectorDrawableState(
135                new AnimatedVectorDrawableState(null));
136    }
137
138    private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res,
139            Theme theme) {
140        // TODO: Correctly handle the constant state for AVD.
141        mAnimatedVectorState = new AnimatedVectorDrawableState(state);
142        if (theme != null && canApplyTheme()) {
143            applyTheme(theme);
144        }
145    }
146
147    @Override
148    public ConstantState getConstantState() {
149        return null;
150    }
151
152    @Override
153    public void draw(Canvas canvas) {
154        mAnimatedVectorState.mVectorDrawable.draw(canvas);
155        if (isStarted()) {
156            invalidateSelf();
157        }
158    }
159
160    @Override
161    protected void onBoundsChange(Rect bounds) {
162        mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
163    }
164
165    @Override
166    public int getAlpha() {
167        return mAnimatedVectorState.mVectorDrawable.getAlpha();
168    }
169
170    @Override
171    public void setAlpha(int alpha) {
172        mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
173    }
174
175    @Override
176    public void setColorFilter(ColorFilter colorFilter) {
177        mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
178    }
179
180    @Override
181    public int getOpacity() {
182        return mAnimatedVectorState.mVectorDrawable.getOpacity();
183    }
184
185    @Override
186    public int getIntrinsicWidth() {
187        return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
188    }
189
190    @Override
191    public int getIntrinsicHeight() {
192        return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
193    }
194
195    @Override
196    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
197            throws XmlPullParserException, IOException {
198
199        int eventType = parser.getEventType();
200        while (eventType != XmlPullParser.END_DOCUMENT) {
201            if (eventType == XmlPullParser.START_TAG) {
202                final String tagName = parser.getName();
203                if (ANIMATED_VECTOR.equals(tagName)) {
204                    final TypedArray a = obtainAttributes(res, theme, attrs,
205                            R.styleable.AnimatedVectorDrawable);
206                    int drawableRes = a.getResourceId(
207                            R.styleable.AnimatedVectorDrawable_drawable, 0);
208                    if (drawableRes != 0) {
209                        mAnimatedVectorState.mVectorDrawable = (VectorDrawable) res.getDrawable(
210                                drawableRes, theme).mutate();
211                        mAnimatedVectorState.mVectorDrawable.setAllowCaching(false);
212                    }
213                    a.recycle();
214                } else if (TARGET.equals(tagName)) {
215                    final TypedArray a = obtainAttributes(res, theme, attrs,
216                            R.styleable.AnimatedVectorDrawableTarget);
217                    final String target = a.getString(
218                            R.styleable.AnimatedVectorDrawableTarget_name);
219
220                    int id = a.getResourceId(
221                            R.styleable.AnimatedVectorDrawableTarget_animation, 0);
222                    if (id != 0) {
223                        Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id);
224                        setupAnimatorsForTarget(target, objectAnimator);
225                    }
226                    a.recycle();
227                }
228            }
229
230            eventType = parser.next();
231        }
232    }
233
234    @Override
235    public boolean canApplyTheme() {
236        return super.canApplyTheme() || mAnimatedVectorState != null
237                && mAnimatedVectorState.mVectorDrawable != null
238                && mAnimatedVectorState.mVectorDrawable.canApplyTheme();
239    }
240
241    @Override
242    public void applyTheme(Theme t) {
243        super.applyTheme(t);
244
245        final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable;
246        if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
247            vectorDrawable.applyTheme(t);
248        }
249    }
250
251    private static class AnimatedVectorDrawableState extends ConstantState {
252        int mChangingConfigurations;
253        VectorDrawable mVectorDrawable;
254        ArrayList<Animator> mAnimators;
255
256        public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) {
257            if (copy != null) {
258                mChangingConfigurations = copy.mChangingConfigurations;
259                // TODO: Make sure the constant state are handled correctly.
260                mVectorDrawable = new VectorDrawable();
261                mVectorDrawable.setAllowCaching(false);
262                mAnimators = new ArrayList<Animator>();
263            }
264        }
265
266        @Override
267        public Drawable newDrawable() {
268            return new AnimatedVectorDrawable(this, null, null);
269        }
270
271        @Override
272        public Drawable newDrawable(Resources res) {
273            return new AnimatedVectorDrawable(this, res, null);
274        }
275
276        @Override
277        public Drawable newDrawable(Resources res, Theme theme) {
278            return new AnimatedVectorDrawable(this, res, theme);
279        }
280
281        @Override
282        public int getChangingConfigurations() {
283            return mChangingConfigurations;
284        }
285    }
286
287    private void setupAnimatorsForTarget(String name, Animator animator) {
288        Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
289        animator.setTarget(target);
290        mAnimatedVectorState.mAnimators.add(animator);
291        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
292            Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
293        }
294    }
295
296    @Override
297    public boolean isRunning() {
298        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
299        final int size = animators.size();
300        for (int i = 0; i < size; i++) {
301            final Animator animator = animators.get(i);
302            if (animator.isRunning()) {
303                return true;
304            }
305        }
306        return false;
307    }
308
309    private boolean isStarted() {
310        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
311        final int size = animators.size();
312        for (int i = 0; i < size; i++) {
313            final Animator animator = animators.get(i);
314            if (animator.isStarted()) {
315                return true;
316            }
317        }
318        return false;
319    }
320
321    @Override
322    public void start() {
323        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
324        final int size = animators.size();
325        for (int i = 0; i < size; i++) {
326            final Animator animator = animators.get(i);
327            if (!animator.isStarted()) {
328                animator.start();
329            }
330        }
331        invalidateSelf();
332    }
333
334    @Override
335    public void stop() {
336        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
337        final int size = animators.size();
338        for (int i = 0; i < size; i++) {
339            final Animator animator = animators.get(i);
340            animator.end();
341        }
342    }
343
344    /**
345     * Reverses ongoing animations or starts pending animations in reverse.
346     * <p>
347     * NOTE: Only works of all animations are ValueAnimators.
348     * @hide
349     */
350    public void reverse() {
351        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
352        final int size = animators.size();
353        for (int i = 0; i < size; i++) {
354            final Animator animator = animators.get(i);
355            if (animator.canReverse()) {
356                animator.reverse();
357            } else {
358                Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
359            }
360        }
361    }
362
363    /**
364     * @hide
365     */
366    public boolean canReverse() {
367        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
368        final int size = animators.size();
369        for (int i = 0; i < size; i++) {
370            final Animator animator = animators.get(i);
371            if (!animator.canReverse()) {
372                return false;
373            }
374        }
375        return true;
376    }
377}
378