OSDN Git Service

Fixing AVD animation with startOffset
[android-x86/frameworks-base.git] / graphics / java / android / graphics / drawable / AnimatedVectorDrawable.java
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
15 package android.graphics.drawable;
16
17 import android.animation.Animator;
18 import android.animation.AnimatorInflater;
19 import android.animation.ValueAnimator;
20 import android.content.res.Resources;
21 import android.content.res.Resources.Theme;
22 import android.content.res.TypedArray;
23 import android.graphics.Canvas;
24 import android.graphics.ColorFilter;
25 import android.graphics.Rect;
26 import android.util.AttributeSet;
27 import android.util.Log;
28
29 import com.android.internal.R;
30
31 import org.xmlpull.v1.XmlPullParser;
32 import org.xmlpull.v1.XmlPullParserException;
33
34 import java.io.IOException;
35 import 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&quot;
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&quot;
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&quot;&gt;
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  */
122 public 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 }