AnimatedVectorDrawable.java revision 9cb5b4c2d93acb9d6f5e14167e265c328c487d6b
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.content.res.Resources;
20import android.content.res.Resources.Theme;
21import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.ColorFilter;
24import android.graphics.Rect;
25import android.util.AttributeSet;
26import android.util.Log;
27
28import com.android.internal.R;
29
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32
33import java.io.IOException;
34import java.util.ArrayList;
35
36/**
37 * This class uses {@link android.animation.ObjectAnimator} and
38 * {@link android.animation.AnimatorSet} to animate the properties of a
39 * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable.
40 * <p>
41 * AnimatedVectorDrawable are normally defined as 3 separate XML files.
42 * </p>
43 * <p>
44 * First is the XML file for {@link android.graphics.drawable.VectorDrawable}.
45 * Note that we allow the animation happen on the group's attributes and path's
46 * attributes, which requires they are uniquely named in this xml file. Groups
47 * and paths without animations do not need names.
48 * </p>
49 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
50 * <pre>
51 * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android"; &gt;
52 *     &lt;size
53 *         android:height=&quot;64dp&quot;
54 *         android:width=&quot;64dp&quot; /&gt;
55 *     &lt;viewport
56 *         android:viewportHeight=&quot;600&quot;
57 *         android:viewportWidth=&quot;600&quot; /&gt;
58 *     &lt;group
59 *         android:name=&quot;rotationGroup&quot;
60 *         android:pivotX=&quot;300.0&quot;
61 *         android:pivotY=&quot;300.0&quot;
62 *         android:rotation=&quot;45.0&quot; &gt;
63 *         &lt;path
64 *             android:name=&quot;v&quot;
65 *             android:fill=&quot;#000000&quot;
66 *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
67 *     &lt;/group&gt;
68 * &lt;/vector&gt;
69 * </pre></li>
70 * <p>
71 * Second is the AnimatedVectorDrawable's xml file, which defines the target
72 * VectorDrawable, the target paths and groups to animate, the properties of the
73 * path and group to animate and the animations defined as the ObjectAnimators
74 * or AnimatorSets.
75 * </p>
76 * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
77 * Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
78 * <pre>
79 * &lt;animated-vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
80 *   android:drawable=&quot;@drawable/vectordrawable&quot; &gt;
81 *     &lt;target
82 *         android:name=&quot;rotationGroup&quot;
83 *         android:animation=&quot;@anim/rotation&quot; /&gt;
84 *     &lt;target
85 *         android:name=&quot;v&quot;
86 *         android:animation=&quot;@anim/path_morph&quot; /&gt;
87 * &lt;/animated-vector&gt;
88 * </pre></li>
89 * <p>
90 * Last is the Animator xml file, which is the same as a normal ObjectAnimator
91 * or AnimatorSet.
92 * To complete this example, here are the 2 animator files used in avd.xml:
93 * rotation.xml and path_morph.xml.
94 * </p>
95 * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees.
96 * <pre>
97 * &lt;objectAnimator
98 *     android:duration=&quot;6000&quot;
99 *     android:propertyName=&quot;rotation&quot;
100 *     android:valueFrom=&quot;0&quot;
101 *     android:valueTo=&quot;360&quot; /&gt;
102 * </pre></li>
103 * <li>Here is the path_morph.xml, which will morph the path from one shape to
104 * the other. Note that the paths must be compatible for morphing.
105 * In more details, the paths should have exact same length of commands , and
106 * exact same length of parameters for each commands.
107 * Note that the path string are better stored in strings.xml for reusing.
108 * <pre>
109 * &lt;set xmlns:android=&quot;http://schemas.android.com/apk/res/android">;
110 *     &lt;objectAnimator
111 *         android:duration=&quot;3000&quot;
112 *         android:propertyName=&quot;pathData&quot;
113 *         android:valueFrom=&quot;M300,70 l 0,-70 70,70 0,0   -70,70z&quot;
114 *         android:valueTo=&quot;M300,70 l 0,-70 70,0  0,140 -70,0 z&quot;
115 *         android:valueType=&quot;pathType&quot;/&gt;
116 * &lt;/set&gt;
117 * </pre></li>
118 *
119 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable
120 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name
121 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
122 */
123public class AnimatedVectorDrawable extends Drawable implements Animatable {
124    private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName();
125
126    private static final String ANIMATED_VECTOR = "animated-vector";
127    private static final String TARGET = "target";
128
129    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
130
131    private final AnimatedVectorDrawableState mAnimatedVectorState;
132
133
134    public AnimatedVectorDrawable() {
135        mAnimatedVectorState = new AnimatedVectorDrawableState(
136                new AnimatedVectorDrawableState(null));
137    }
138
139    private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res,
140            Theme theme) {
141        // TODO: Correctly handle the constant state for AVD.
142        mAnimatedVectorState = new AnimatedVectorDrawableState(state);
143        if (theme != null && canApplyTheme()) {
144            applyTheme(theme);
145        }
146    }
147
148    @Override
149    public ConstantState getConstantState() {
150        return null;
151    }
152
153    @Override
154    public void draw(Canvas canvas) {
155        mAnimatedVectorState.mVectorDrawable.draw(canvas);
156        if (isRunning()) {
157            invalidateSelf();
158        }
159    }
160
161    @Override
162    protected void onBoundsChange(Rect bounds) {
163        mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
164    }
165
166    @Override
167    public int getAlpha() {
168        return mAnimatedVectorState.mVectorDrawable.getAlpha();
169    }
170
171    @Override
172    public void setAlpha(int alpha) {
173        mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
174    }
175
176    @Override
177    public void setColorFilter(ColorFilter colorFilter) {
178        mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
179    }
180
181    @Override
182    public int getOpacity() {
183        return mAnimatedVectorState.mVectorDrawable.getOpacity();
184    }
185
186    @Override
187    public int getIntrinsicWidth() {
188        return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
189    }
190
191    @Override
192    public int getIntrinsicHeight() {
193        return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
194    }
195
196    @Override
197    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
198            throws XmlPullParserException, IOException {
199
200        int eventType = parser.getEventType();
201        while (eventType != XmlPullParser.END_DOCUMENT) {
202            if (eventType == XmlPullParser.START_TAG) {
203                final String tagName = parser.getName();
204                if (ANIMATED_VECTOR.equals(tagName)) {
205                    final TypedArray a = obtainAttributes(res, theme, attrs,
206                            R.styleable.AnimatedVectorDrawable);
207                    int drawableRes = a.getResourceId(
208                            R.styleable.AnimatedVectorDrawable_drawable, 0);
209                    if (drawableRes != 0) {
210                        mAnimatedVectorState.mVectorDrawable = (VectorDrawable) res.getDrawable(
211                                drawableRes, theme);
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                mAnimators = new ArrayList<Animator>();
262            }
263        }
264
265        @Override
266        public Drawable newDrawable() {
267            return new AnimatedVectorDrawable(this, null, null);
268        }
269
270        @Override
271        public Drawable newDrawable(Resources res) {
272            return new AnimatedVectorDrawable(this, res, null);
273        }
274
275        @Override
276        public Drawable newDrawable(Resources res, Theme theme) {
277            return new AnimatedVectorDrawable(this, res, theme);
278        }
279
280        @Override
281        public int getChangingConfigurations() {
282            return mChangingConfigurations;
283        }
284    }
285
286    private void setupAnimatorsForTarget(String name, Animator animator) {
287        Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
288        animator.setTarget(target);
289        mAnimatedVectorState.mAnimators.add(animator);
290        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
291            Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
292        }
293    }
294
295    @Override
296    public boolean isRunning() {
297        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
298        final int size = animators.size();
299        for (int i = 0; i < size; i++) {
300            final Animator animator = animators.get(i);
301            if (animator.isRunning()) {
302                return true;
303            }
304        }
305        return false;
306    }
307
308    @Override
309    public void start() {
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.isPaused()) {
315                animator.resume();
316            } else if (!animator.isRunning()) {
317                animator.start();
318            }
319        }
320        invalidateSelf();
321    }
322
323    @Override
324    public void stop() {
325        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
326        final int size = animators.size();
327        for (int i = 0; i < size; i++) {
328            final Animator animator = animators.get(i);
329            animator.pause();
330        }
331    }
332}
333