• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.res.ColorStateList;
18 import android.content.res.Resources;
19 import android.content.res.Resources.Theme;
20 import android.content.res.TypedArray;
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.Color;
24 import android.graphics.ColorFilter;
25 import android.graphics.Matrix;
26 import android.graphics.Paint;
27 import android.graphics.Path;
28 import android.graphics.PathMeasure;
29 import android.graphics.PixelFormat;
30 import android.graphics.PorterDuffColorFilter;
31 import android.graphics.Rect;
32 import android.graphics.Region;
33 import android.graphics.PorterDuff.Mode;
34 import android.util.ArrayMap;
35 import android.util.AttributeSet;
36 import android.util.LayoutDirection;
37 import android.util.Log;
38 import android.util.PathParser;
39 import android.util.Xml;
40 
41 import com.android.internal.R;
42 
43 import org.xmlpull.v1.XmlPullParser;
44 import org.xmlpull.v1.XmlPullParserException;
45 
46 import java.io.IOException;
47 import java.util.ArrayList;
48 import java.util.Stack;
49 
50 /**
51  * This lets you create a drawable based on an XML vector graphic. It can be
52  * defined in an XML file with the <code>&lt;vector></code> element.
53  * <p/>
54  * The vector drawable has the following elements:
55  * <p/>
56  * <dt><code>&lt;vector></code></dt>
57  * <dl>
58  * <dd>Used to defined a vector drawable
59  * <dl>
60  * <dt><code>android:name</code></dt>
61  * <dd>Defines the name of this vector drawable.</dd>
62  * <dt><code>android:width</code></dt>
63  * <dd>Used to defined the intrinsic width of the drawable.
64  * This support all the dimension units, normally specified with dp.</dd>
65  * <dt><code>android:height</code></dt>
66  * <dd>Used to defined the intrinsic height the drawable.
67  * This support all the dimension units, normally specified with dp.</dd>
68  * <dt><code>android:viewportWidth</code></dt>
69  * <dd>Used to defined the width of the viewport space. Viewport is basically
70  * the virtual canvas where the paths are drawn on.</dd>
71  * <dt><code>android:viewportHeight</code></dt>
72  * <dd>Used to defined the height of the viewport space. Viewport is basically
73  * the virtual canvas where the paths are drawn on.</dd>
74  * <dt><code>android:tint</code></dt>
75  * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd>
76  * <dt><code>android:tintMode</code></dt>
77  * <dd>The Porter-Duff blending mode for the tint color. The default value is src_in.</dd>
78  * <dt><code>android:autoMirrored</code></dt>
79  * <dd>Indicates if the drawable needs to be mirrored when its layout direction is
80  * RTL (right-to-left).</dd>
81  * <dt><code>android:alpha</code></dt>
82  * <dd>The opacity of this drawable.</dd>
83  * </dl></dd>
84  * </dl>
85  *
86  * <dl>
87  * <dt><code>&lt;group></code></dt>
88  * <dd>Defines a group of paths or subgroups, plus transformation information.
89  * The transformations are defined in the same coordinates as the viewport.
90  * And the transformations are applied in the order of scale, rotate then translate.
91  * <dl>
92  * <dt><code>android:name</code></dt>
93  * <dd>Defines the name of the group.</dd>
94  * <dt><code>android:rotation</code></dt>
95  * <dd>The degrees of rotation of the group.</dd>
96  * <dt><code>android:pivotX</code></dt>
97  * <dd>The X coordinate of the pivot for the scale and rotation of the group.
98  * This is defined in the viewport space.</dd>
99  * <dt><code>android:pivotY</code></dt>
100  * <dd>The Y coordinate of the pivot for the scale and rotation of the group.
101  * This is defined in the viewport space.</dd>
102  * <dt><code>android:scaleX</code></dt>
103  * <dd>The amount of scale on the X Coordinate.</dd>
104  * <dt><code>android:scaleY</code></dt>
105  * <dd>The amount of scale on the Y coordinate.</dd>
106  * <dt><code>android:translateX</code></dt>
107  * <dd>The amount of translation on the X coordinate.
108  * This is defined in the viewport space.</dd>
109  * <dt><code>android:translateY</code></dt>
110  * <dd>The amount of translation on the Y coordinate.
111  * This is defined in the viewport space.</dd>
112  * </dl></dd>
113  * </dl>
114  *
115  * <dl>
116  * <dt><code>&lt;path></code></dt>
117  * <dd>Defines paths to be drawn.
118  * <dl>
119  * <dt><code>android:name</code></dt>
120  * <dd>Defines the name of the path.</dd>
121  * <dt><code>android:pathData</code></dt>
122  * <dd>Defines path string. This is using exactly same format as "d" attribute
123  * in the SVG's path data. This is defined in the viewport space.</dd>
124  * <dt><code>android:fillColor</code></dt>
125  * <dd>Defines the color to fill the path (none if not present).</dd>
126  * <dt><code>android:strokeColor</code></dt>
127  * <dd>Defines the color to draw the path outline (none if not present).</dd>
128  * <dt><code>android:strokeWidth</code></dt>
129  * <dd>The width a path stroke.</dd>
130  * <dt><code>android:strokeAlpha</code></dt>
131  * <dd>The opacity of a path stroke.</dd>
132  * <dt><code>android:fillAlpha</code></dt>
133  * <dd>The opacity to fill the path with.</dd>
134  * <dt><code>android:trimPathStart</code></dt>
135  * <dd>The fraction of the path to trim from the start, in the range from 0 to 1.</dd>
136  * <dt><code>android:trimPathEnd</code></dt>
137  * <dd>The fraction of the path to trim from the end, in the range from 0 to 1.</dd>
138  * <dt><code>android:trimPathOffset</code></dt>
139  * <dd>Shift trim region (allows showed region to include the start and end), in the range
140  * from 0 to 1.</dd>
141  * <dt><code>android:strokeLineCap</code></dt>
142  * <dd>Sets the linecap for a stroked path: butt, round, square.</dd>
143  * <dt><code>android:strokeLineJoin</code></dt>
144  * <dd>Sets the lineJoin for a stroked path: miter,round,bevel.</dd>
145  * <dt><code>android:strokeMiterLimit</code></dt>
146  * <dd>Sets the Miter limit for a stroked path.</dd>
147  * </dl></dd>
148  * </dl>
149  *
150  * <dl>
151  * <dt><code>&lt;clip-path></code></dt>
152  * <dd>Defines path to be the current clip.
153  * <dl>
154  * <dt><code>android:name</code></dt>
155  * <dd>Defines the name of the clip path.</dd>
156  * <dt><code>android:pathData</code></dt>
157  * <dd>Defines clip path string. This is using exactly same format as "d" attribute
158  * in the SVG's path data.</dd>
159  * </dl></dd>
160  * </dl>
161  * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
162  * <pre>
163  * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
164  *     android:height=&quot;64dp&quot;
165  *     android:width=&quot;64dp&quot;
166  *     android:viewportHeight=&quot;600&quot;
167  *     android:viewportWidth=&quot;600&quot; &gt;
168  *     &lt;group
169  *         android:name=&quot;rotationGroup&quot;
170  *         android:pivotX=&quot;300.0&quot;
171  *         android:pivotY=&quot;300.0&quot;
172  *         android:rotation=&quot;45.0&quot; &gt;
173  *         &lt;path
174  *             android:name=&quot;v&quot;
175  *             android:fillColor=&quot;#000000&quot;
176  *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
177  *     &lt;/group&gt;
178  * &lt;/vector&gt;
179  * </pre></li>
180  */
181 
182 public class VectorDrawable extends Drawable {
183     private static final String LOGTAG = VectorDrawable.class.getSimpleName();
184 
185     private static final String SHAPE_CLIP_PATH = "clip-path";
186     private static final String SHAPE_GROUP = "group";
187     private static final String SHAPE_PATH = "path";
188     private static final String SHAPE_VECTOR = "vector";
189 
190     private static final int LINECAP_BUTT = 0;
191     private static final int LINECAP_ROUND = 1;
192     private static final int LINECAP_SQUARE = 2;
193 
194     private static final int LINEJOIN_MITER = 0;
195     private static final int LINEJOIN_ROUND = 1;
196     private static final int LINEJOIN_BEVEL = 2;
197 
198     private static final boolean DBG_VECTOR_DRAWABLE = false;
199 
200     private VectorDrawableState mVectorState;
201 
202     private PorterDuffColorFilter mTintFilter;
203     private ColorFilter mColorFilter;
204 
205     private boolean mMutated;
206 
207     // AnimatedVectorDrawable needs to turn off the cache all the time, otherwise,
208     // caching the bitmap by default is allowed.
209     private boolean mAllowCaching = true;
210 
VectorDrawable()211     public VectorDrawable() {
212         mVectorState = new VectorDrawableState();
213     }
214 
VectorDrawable(VectorDrawableState state, Resources res, Theme theme)215     private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) {
216         if (theme != null && state.canApplyTheme()) {
217             // If we need to apply a theme, implicitly mutate.
218             mVectorState = new VectorDrawableState(state);
219             applyTheme(theme);
220         } else {
221             mVectorState = state;
222         }
223 
224         mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
225     }
226 
227     @Override
mutate()228     public Drawable mutate() {
229         if (!mMutated && super.mutate() == this) {
230             mVectorState = new VectorDrawableState(mVectorState);
231             mMutated = true;
232         }
233         return this;
234     }
235 
getTargetByName(String name)236     Object getTargetByName(String name) {
237         return mVectorState.mVPathRenderer.mVGTargetsMap.get(name);
238     }
239 
240     @Override
getConstantState()241     public ConstantState getConstantState() {
242         mVectorState.mChangingConfigurations = getChangingConfigurations();
243         return mVectorState;
244     }
245 
246     @Override
draw(Canvas canvas)247     public void draw(Canvas canvas) {
248         final Rect bounds = getBounds();
249         if (bounds.width() == 0 || bounds.height() == 0) {
250             // too small to draw
251             return;
252         }
253 
254         final int saveCount = canvas.save();
255         final boolean needMirroring = needMirroring();
256 
257         canvas.translate(bounds.left, bounds.top);
258         if (needMirroring) {
259             canvas.translate(bounds.width(), 0);
260             canvas.scale(-1.0f, 1.0f);
261         }
262 
263         // Color filters always override tint filters.
264         final ColorFilter colorFilter = mColorFilter == null ? mTintFilter : mColorFilter;
265 
266         if (!mAllowCaching) {
267             // AnimatedVectorDrawable
268             if (!mVectorState.hasTranslucentRoot()) {
269                 mVectorState.mVPathRenderer.draw(
270                         canvas, bounds.width(), bounds.height(), colorFilter);
271             } else {
272                 mVectorState.createCachedBitmapIfNeeded(bounds);
273                 mVectorState.updateCachedBitmap(bounds);
274                 mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter);
275             }
276         } else {
277             // Static Vector Drawable case.
278             mVectorState.createCachedBitmapIfNeeded(bounds);
279             if (!mVectorState.canReuseCache()) {
280                 mVectorState.updateCachedBitmap(bounds);
281                 mVectorState.updateCacheStates();
282             }
283             mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter);
284         }
285 
286         canvas.restoreToCount(saveCount);
287     }
288 
289     @Override
getAlpha()290     public int getAlpha() {
291         return mVectorState.mVPathRenderer.getRootAlpha();
292     }
293 
294     @Override
setAlpha(int alpha)295     public void setAlpha(int alpha) {
296         if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) {
297             mVectorState.mVPathRenderer.setRootAlpha(alpha);
298             invalidateSelf();
299         }
300     }
301 
302     @Override
setColorFilter(ColorFilter colorFilter)303     public void setColorFilter(ColorFilter colorFilter) {
304         mColorFilter = colorFilter;
305         invalidateSelf();
306     }
307 
308     @Override
setTintList(ColorStateList tint)309     public void setTintList(ColorStateList tint) {
310         final VectorDrawableState state = mVectorState;
311         if (state.mTint != tint) {
312             state.mTint = tint;
313             mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode);
314             invalidateSelf();
315         }
316     }
317 
318     @Override
setTintMode(Mode tintMode)319     public void setTintMode(Mode tintMode) {
320         final VectorDrawableState state = mVectorState;
321         if (state.mTintMode != tintMode) {
322             state.mTintMode = tintMode;
323             mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode);
324             invalidateSelf();
325         }
326     }
327 
328     @Override
isStateful()329     public boolean isStateful() {
330         return super.isStateful() || (mVectorState != null && mVectorState.mTint != null
331                 && mVectorState.mTint.isStateful());
332     }
333 
334     @Override
onStateChange(int[] stateSet)335     protected boolean onStateChange(int[] stateSet) {
336         final VectorDrawableState state = mVectorState;
337         if (state.mTint != null && state.mTintMode != null) {
338             mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
339             invalidateSelf();
340             return true;
341         }
342         return false;
343     }
344 
345     @Override
getOpacity()346     public int getOpacity() {
347         return PixelFormat.TRANSLUCENT;
348     }
349 
350     @Override
getIntrinsicWidth()351     public int getIntrinsicWidth() {
352         return (int) mVectorState.mVPathRenderer.mBaseWidth;
353     }
354 
355     @Override
getIntrinsicHeight()356     public int getIntrinsicHeight() {
357         return (int) mVectorState.mVPathRenderer.mBaseHeight;
358     }
359 
360     @Override
canApplyTheme()361     public boolean canApplyTheme() {
362         return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme();
363     }
364 
365     @Override
applyTheme(Theme t)366     public void applyTheme(Theme t) {
367         super.applyTheme(t);
368 
369         final VectorDrawableState state = mVectorState;
370         if (state != null && state.mThemeAttrs != null) {
371             final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.VectorDrawable);
372             try {
373                 state.mCacheDirty = true;
374                 updateStateFromTypedArray(a);
375             } catch (XmlPullParserException e) {
376                 throw new RuntimeException(e);
377             } finally {
378                 a.recycle();
379             }
380 
381             mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
382         }
383 
384         final VPathRenderer path = state.mVPathRenderer;
385         if (path != null && path.canApplyTheme()) {
386             path.applyTheme(t);
387         }
388     }
389 
390     /**
391      * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension.
392      * This is used to calculate the path animation accuracy.
393      *
394      * @hide
395      */
getPixelSize()396     public float getPixelSize() {
397         if (mVectorState == null && mVectorState.mVPathRenderer == null ||
398                 mVectorState.mVPathRenderer.mBaseWidth == 0 ||
399                 mVectorState.mVPathRenderer.mBaseHeight == 0 ||
400                 mVectorState.mVPathRenderer.mViewportHeight == 0 ||
401                 mVectorState.mVPathRenderer.mViewportWidth == 0) {
402             return 1; // fall back to 1:1 pixel mapping.
403         }
404         float intrinsicWidth = mVectorState.mVPathRenderer.mBaseWidth;
405         float intrinsicHeight = mVectorState.mVPathRenderer.mBaseHeight;
406         float viewportWidth = mVectorState.mVPathRenderer.mViewportWidth;
407         float viewportHeight = mVectorState.mVPathRenderer.mViewportHeight;
408         float scaleX = viewportWidth / intrinsicWidth;
409         float scaleY = viewportHeight / intrinsicHeight;
410         return Math.min(scaleX, scaleY);
411     }
412 
413     /** @hide */
create(Resources resources, int rid)414     public static VectorDrawable create(Resources resources, int rid) {
415         try {
416             final XmlPullParser parser = resources.getXml(rid);
417             final AttributeSet attrs = Xml.asAttributeSet(parser);
418             int type;
419             while ((type=parser.next()) != XmlPullParser.START_TAG &&
420                     type != XmlPullParser.END_DOCUMENT) {
421                 // Empty loop
422             }
423             if (type != XmlPullParser.START_TAG) {
424                 throw new XmlPullParserException("No start tag found");
425             }
426 
427             final VectorDrawable drawable = new VectorDrawable();
428             drawable.inflate(resources, parser, attrs);
429 
430             return drawable;
431         } catch (XmlPullParserException e) {
432             Log.e(LOGTAG, "parser error", e);
433         } catch (IOException e) {
434             Log.e(LOGTAG, "parser error", e);
435         }
436         return null;
437     }
438 
applyAlpha(int color, float alpha)439     private static int applyAlpha(int color, float alpha) {
440         int alphaBytes = Color.alpha(color);
441         color &= 0x00FFFFFF;
442         color |= ((int) (alphaBytes * alpha)) << 24;
443         return color;
444     }
445 
446     @Override
inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)447     public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
448             throws XmlPullParserException, IOException {
449         final VectorDrawableState state = mVectorState;
450         final VPathRenderer pathRenderer = new VPathRenderer();
451         state.mVPathRenderer = pathRenderer;
452 
453         final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable);
454         updateStateFromTypedArray(a);
455         a.recycle();
456 
457         state.mCacheDirty = true;
458         inflateInternal(res, parser, attrs, theme);
459 
460         mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
461     }
462 
updateStateFromTypedArray(TypedArray a)463     private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
464         final VectorDrawableState state = mVectorState;
465         final VPathRenderer pathRenderer = state.mVPathRenderer;
466 
467         // Account for any configuration changes.
468         state.mChangingConfigurations |= a.getChangingConfigurations();
469 
470         // Extract the theme attributes, if any.
471         state.mThemeAttrs = a.extractThemeAttrs();
472 
473         final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
474         if (tintMode != -1) {
475             state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
476         }
477 
478         final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
479         if (tint != null) {
480             state.mTint = tint;
481         }
482 
483         state.mAutoMirrored = a.getBoolean(
484                 R.styleable.VectorDrawable_autoMirrored, state.mAutoMirrored);
485 
486         pathRenderer.mViewportWidth = a.getFloat(
487                 R.styleable.VectorDrawable_viewportWidth, pathRenderer.mViewportWidth);
488         pathRenderer.mViewportHeight = a.getFloat(
489                 R.styleable.VectorDrawable_viewportHeight, pathRenderer.mViewportHeight);
490 
491         if (pathRenderer.mViewportWidth <= 0) {
492             throw new XmlPullParserException(a.getPositionDescription() +
493                     "<vector> tag requires viewportWidth > 0");
494         } else if (pathRenderer.mViewportHeight <= 0) {
495             throw new XmlPullParserException(a.getPositionDescription() +
496                     "<vector> tag requires viewportHeight > 0");
497         }
498 
499         pathRenderer.mBaseWidth = a.getDimension(
500                 R.styleable.VectorDrawable_width, pathRenderer.mBaseWidth);
501         pathRenderer.mBaseHeight = a.getDimension(
502                 R.styleable.VectorDrawable_height, pathRenderer.mBaseHeight);
503 
504         if (pathRenderer.mBaseWidth <= 0) {
505             throw new XmlPullParserException(a.getPositionDescription() +
506                     "<vector> tag requires width > 0");
507         } else if (pathRenderer.mBaseHeight <= 0) {
508             throw new XmlPullParserException(a.getPositionDescription() +
509                     "<vector> tag requires height > 0");
510         }
511 
512         final float alphaInFloat = a.getFloat(R.styleable.VectorDrawable_alpha,
513                 pathRenderer.getAlpha());
514         pathRenderer.setAlpha(alphaInFloat);
515 
516         final String name = a.getString(R.styleable.VectorDrawable_name);
517         if (name != null) {
518             pathRenderer.mRootName = name;
519             pathRenderer.mVGTargetsMap.put(name, pathRenderer);
520         }
521     }
522 
inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)523     private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
524             Theme theme) throws XmlPullParserException, IOException {
525         final VectorDrawableState state = mVectorState;
526         final VPathRenderer pathRenderer = state.mVPathRenderer;
527         boolean noPathTag = true;
528 
529         // Use a stack to help to build the group tree.
530         // The top of the stack is always the current group.
531         final Stack<VGroup> groupStack = new Stack<VGroup>();
532         groupStack.push(pathRenderer.mRootGroup);
533 
534         int eventType = parser.getEventType();
535         while (eventType != XmlPullParser.END_DOCUMENT) {
536             if (eventType == XmlPullParser.START_TAG) {
537                 final String tagName = parser.getName();
538                 final VGroup currentGroup = groupStack.peek();
539 
540                 if (SHAPE_PATH.equals(tagName)) {
541                     final VFullPath path = new VFullPath();
542                     path.inflate(res, attrs, theme);
543                     currentGroup.mChildren.add(path);
544                     if (path.getPathName() != null) {
545                         pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
546                     }
547                     noPathTag = false;
548                     state.mChangingConfigurations |= path.mChangingConfigurations;
549                 } else if (SHAPE_CLIP_PATH.equals(tagName)) {
550                     final VClipPath path = new VClipPath();
551                     path.inflate(res, attrs, theme);
552                     currentGroup.mChildren.add(path);
553                     if (path.getPathName() != null) {
554                         pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
555                     }
556                     state.mChangingConfigurations |= path.mChangingConfigurations;
557                 } else if (SHAPE_GROUP.equals(tagName)) {
558                     VGroup newChildGroup = new VGroup();
559                     newChildGroup.inflate(res, attrs, theme);
560                     currentGroup.mChildren.add(newChildGroup);
561                     groupStack.push(newChildGroup);
562                     if (newChildGroup.getGroupName() != null) {
563                         pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(),
564                                 newChildGroup);
565                     }
566                     state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
567                 }
568             } else if (eventType == XmlPullParser.END_TAG) {
569                 final String tagName = parser.getName();
570                 if (SHAPE_GROUP.equals(tagName)) {
571                     groupStack.pop();
572                 }
573             }
574             eventType = parser.next();
575         }
576 
577         // Print the tree out for debug.
578         if (DBG_VECTOR_DRAWABLE) {
579             printGroupTree(pathRenderer.mRootGroup, 0);
580         }
581 
582         if (noPathTag) {
583             final StringBuffer tag = new StringBuffer();
584 
585             if (tag.length() > 0) {
586                 tag.append(" or ");
587             }
588             tag.append(SHAPE_PATH);
589 
590             throw new XmlPullParserException("no " + tag + " defined");
591         }
592     }
593 
printGroupTree(VGroup currentGroup, int level)594     private void printGroupTree(VGroup currentGroup, int level) {
595         String indent = "";
596         for (int i = 0; i < level; i++) {
597             indent += "    ";
598         }
599         // Print the current node
600         Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName()
601                 + " rotation is " + currentGroup.mRotate);
602         Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString());
603         // Then print all the children groups
604         for (int i = 0; i < currentGroup.mChildren.size(); i++) {
605             Object child = currentGroup.mChildren.get(i);
606             if (child instanceof VGroup) {
607                 printGroupTree((VGroup) child, level + 1);
608             }
609         }
610     }
611 
612     @Override
getChangingConfigurations()613     public int getChangingConfigurations() {
614         return super.getChangingConfigurations() | mVectorState.mChangingConfigurations;
615     }
616 
setAllowCaching(boolean allowCaching)617     void setAllowCaching(boolean allowCaching) {
618         mAllowCaching = allowCaching;
619     }
620 
needMirroring()621     private boolean needMirroring() {
622         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
623     }
624 
625     @Override
setAutoMirrored(boolean mirrored)626     public void setAutoMirrored(boolean mirrored) {
627         if (mVectorState.mAutoMirrored != mirrored) {
628             mVectorState.mAutoMirrored = mirrored;
629             invalidateSelf();
630         }
631     }
632 
633     @Override
isAutoMirrored()634     public boolean isAutoMirrored() {
635         return mVectorState.mAutoMirrored;
636     }
637 
638     private static class VectorDrawableState extends ConstantState {
639         int[] mThemeAttrs;
640         int mChangingConfigurations;
641         VPathRenderer mVPathRenderer;
642         ColorStateList mTint = null;
643         Mode mTintMode = DEFAULT_TINT_MODE;
644         boolean mAutoMirrored;
645 
646         Bitmap mCachedBitmap;
647         int[] mCachedThemeAttrs;
648         ColorStateList mCachedTint;
649         Mode mCachedTintMode;
650         int mCachedRootAlpha;
651         boolean mCachedAutoMirrored;
652         boolean mCacheDirty;
653 
654         /** Temporary paint object used to draw cached bitmaps. */
655         Paint mTempPaint;
656 
657         // Deep copy for mutate() or implicitly mutate.
VectorDrawableState(VectorDrawableState copy)658         public VectorDrawableState(VectorDrawableState copy) {
659             if (copy != null) {
660                 mThemeAttrs = copy.mThemeAttrs;
661                 mChangingConfigurations = copy.mChangingConfigurations;
662                 mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
663                 if (copy.mVPathRenderer.mFillPaint != null) {
664                     mVPathRenderer.mFillPaint = new Paint(copy.mVPathRenderer.mFillPaint);
665                 }
666                 if (copy.mVPathRenderer.mStrokePaint != null) {
667                     mVPathRenderer.mStrokePaint = new Paint(copy.mVPathRenderer.mStrokePaint);
668                 }
669                 mTint = copy.mTint;
670                 mTintMode = copy.mTintMode;
671                 mAutoMirrored = copy.mAutoMirrored;
672             }
673         }
674 
drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter)675         public void drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter) {
676             // The bitmap's size is the same as the bounds.
677             final Paint p = getPaint(filter);
678             canvas.drawBitmap(mCachedBitmap, 0, 0, p);
679         }
680 
hasTranslucentRoot()681         public boolean hasTranslucentRoot() {
682             return mVPathRenderer.getRootAlpha() < 255;
683         }
684 
685         /**
686          * @return null when there is no need for alpha paint.
687          */
getPaint(ColorFilter filter)688         public Paint getPaint(ColorFilter filter) {
689             if (!hasTranslucentRoot() && filter == null) {
690                 return null;
691             }
692 
693             if (mTempPaint == null) {
694                 mTempPaint = new Paint();
695                 mTempPaint.setFilterBitmap(true);
696             }
697             mTempPaint.setAlpha(mVPathRenderer.getRootAlpha());
698             mTempPaint.setColorFilter(filter);
699             return mTempPaint;
700         }
701 
updateCachedBitmap(Rect bounds)702         public void updateCachedBitmap(Rect bounds) {
703             mCachedBitmap.eraseColor(Color.TRANSPARENT);
704             Canvas tmpCanvas = new Canvas(mCachedBitmap);
705             mVPathRenderer.draw(tmpCanvas, bounds.width(), bounds.height(), null);
706         }
707 
createCachedBitmapIfNeeded(Rect bounds)708         public void createCachedBitmapIfNeeded(Rect bounds) {
709             if (mCachedBitmap == null || !canReuseBitmap(bounds.width(),
710                     bounds.height())) {
711                 mCachedBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(),
712                         Bitmap.Config.ARGB_8888);
713                 mCacheDirty = true;
714             }
715 
716         }
717 
canReuseBitmap(int width, int height)718         public boolean canReuseBitmap(int width, int height) {
719             if (width == mCachedBitmap.getWidth()
720                     && height == mCachedBitmap.getHeight()) {
721                 return true;
722             }
723             return false;
724         }
725 
canReuseCache()726         public boolean canReuseCache() {
727             if (!mCacheDirty
728                     && mCachedThemeAttrs == mThemeAttrs
729                     && mCachedTint == mTint
730                     && mCachedTintMode == mTintMode
731                     && mCachedAutoMirrored == mAutoMirrored
732                     && mCachedRootAlpha == mVPathRenderer.getRootAlpha()) {
733                 return true;
734             }
735             return false;
736         }
737 
updateCacheStates()738         public void updateCacheStates() {
739             // Use shallow copy here and shallow comparison in canReuseCache(),
740             // likely hit cache miss more, but practically not much difference.
741             mCachedThemeAttrs = mThemeAttrs;
742             mCachedTint = mTint;
743             mCachedTintMode = mTintMode;
744             mCachedRootAlpha = mVPathRenderer.getRootAlpha();
745             mCachedAutoMirrored = mAutoMirrored;
746             mCacheDirty = false;
747         }
748 
749         @Override
canApplyTheme()750         public boolean canApplyTheme() {
751             return super.canApplyTheme() || mThemeAttrs != null
752                     || (mVPathRenderer != null && mVPathRenderer.canApplyTheme());
753         }
754 
VectorDrawableState()755         public VectorDrawableState() {
756             mVPathRenderer = new VPathRenderer();
757         }
758 
759         @Override
newDrawable()760         public Drawable newDrawable() {
761             return new VectorDrawable(this, null, null);
762         }
763 
764         @Override
newDrawable(Resources res)765         public Drawable newDrawable(Resources res) {
766             return new VectorDrawable(this, res, null);
767         }
768 
769         @Override
newDrawable(Resources res, Theme theme)770         public Drawable newDrawable(Resources res, Theme theme) {
771             return new VectorDrawable(this, res, theme);
772         }
773 
774         @Override
getChangingConfigurations()775         public int getChangingConfigurations() {
776             return mChangingConfigurations;
777         }
778     }
779 
780     private static class VPathRenderer {
781         /* Right now the internal data structure is organized as a tree.
782          * Each node can be a group node, or a path.
783          * A group node can have groups or paths as children, but a path node has
784          * no children.
785          * One example can be:
786          *                 Root Group
787          *                /    |     \
788          *           Group    Path    Group
789          *          /     \             |
790          *         Path   Path         Path
791          *
792          */
793         // Variables that only used temporarily inside the draw() call, so there
794         // is no need for deep copying.
795         private final Path mPath;
796         private final Path mRenderPath;
797         private static final Matrix IDENTITY_MATRIX = new Matrix();
798         private final Matrix mFinalPathMatrix = new Matrix();
799 
800         private Paint mStrokePaint;
801         private Paint mFillPaint;
802         private PathMeasure mPathMeasure;
803 
804         /////////////////////////////////////////////////////
805         // Variables below need to be copied (deep copy if applicable) for mutation.
806         private int mChangingConfigurations;
807         private final VGroup mRootGroup;
808         float mBaseWidth = 0;
809         float mBaseHeight = 0;
810         float mViewportWidth = 0;
811         float mViewportHeight = 0;
812         int mRootAlpha = 0xFF;
813         String mRootName = null;
814 
815         final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>();
816 
VPathRenderer()817         public VPathRenderer() {
818             mRootGroup = new VGroup();
819             mPath = new Path();
820             mRenderPath = new Path();
821         }
822 
setRootAlpha(int alpha)823         public void setRootAlpha(int alpha) {
824             mRootAlpha = alpha;
825         }
826 
getRootAlpha()827         public int getRootAlpha() {
828             return mRootAlpha;
829         }
830 
831         // setAlpha() and getAlpha() are used mostly for animation purpose, since
832         // Animator like to use alpha from 0 to 1.
setAlpha(float alpha)833         public void setAlpha(float alpha) {
834             setRootAlpha((int) (alpha * 255));
835         }
836 
837         @SuppressWarnings("unused")
getAlpha()838         public float getAlpha() {
839             return getRootAlpha() / 255.0f;
840         }
841 
VPathRenderer(VPathRenderer copy)842         public VPathRenderer(VPathRenderer copy) {
843             mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
844             mPath = new Path(copy.mPath);
845             mRenderPath = new Path(copy.mRenderPath);
846             mBaseWidth = copy.mBaseWidth;
847             mBaseHeight = copy.mBaseHeight;
848             mViewportWidth = copy.mViewportWidth;
849             mViewportHeight = copy.mViewportHeight;
850             mChangingConfigurations = copy.mChangingConfigurations;
851             mRootAlpha = copy.mRootAlpha;
852             mRootName = copy.mRootName;
853             if (copy.mRootName != null) {
854                 mVGTargetsMap.put(copy.mRootName, this);
855             }
856         }
857 
canApplyTheme()858         public boolean canApplyTheme() {
859             // If one of the paths can apply theme, then return true;
860             return recursiveCanApplyTheme(mRootGroup);
861         }
862 
recursiveCanApplyTheme(VGroup currentGroup)863         private boolean recursiveCanApplyTheme(VGroup currentGroup) {
864             // We can do a tree traverse here, if there is one path return true,
865             // then we return true for the whole tree.
866             final ArrayList<Object> children = currentGroup.mChildren;
867 
868             for (int i = 0; i < children.size(); i++) {
869                 Object child = children.get(i);
870                 if (child instanceof VGroup) {
871                     VGroup childGroup = (VGroup) child;
872                     if (childGroup.canApplyTheme()
873                             || recursiveCanApplyTheme(childGroup)) {
874                         return true;
875                     }
876                 } else if (child instanceof VPath) {
877                     VPath childPath = (VPath) child;
878                     if (childPath.canApplyTheme()) {
879                         return true;
880                     }
881                 }
882             }
883             return false;
884         }
885 
applyTheme(Theme t)886         public void applyTheme(Theme t) {
887             // Apply theme to every path of the tree.
888             recursiveApplyTheme(mRootGroup, t);
889         }
890 
recursiveApplyTheme(VGroup currentGroup, Theme t)891         private void recursiveApplyTheme(VGroup currentGroup, Theme t) {
892             // We can do a tree traverse here, apply theme to all paths which
893             // can apply theme.
894             final ArrayList<Object> children = currentGroup.mChildren;
895             for (int i = 0; i < children.size(); i++) {
896                 Object child = children.get(i);
897                 if (child instanceof VGroup) {
898                     VGroup childGroup = (VGroup) child;
899                     if (childGroup.canApplyTheme()) {
900                         childGroup.applyTheme(t);
901                     }
902                     recursiveApplyTheme(childGroup, t);
903                 } else if (child instanceof VPath) {
904                     VPath childPath = (VPath) child;
905                     if (childPath.canApplyTheme()) {
906                         childPath.applyTheme(t);
907                     }
908                 }
909             }
910         }
911 
drawGroupTree(VGroup currentGroup, Matrix currentMatrix, Canvas canvas, int w, int h, ColorFilter filter)912         private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
913                 Canvas canvas, int w, int h, ColorFilter filter) {
914             // Calculate current group's matrix by preConcat the parent's and
915             // and the current one on the top of the stack.
916             // Basically the Mfinal = Mviewport * M0 * M1 * M2;
917             // Mi the local matrix at level i of the group tree.
918             currentGroup.mStackedMatrix.set(currentMatrix);
919 
920             currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
921 
922             // Draw the group tree in the same order as the XML file.
923             for (int i = 0; i < currentGroup.mChildren.size(); i++) {
924                 Object child = currentGroup.mChildren.get(i);
925                 if (child instanceof VGroup) {
926                     VGroup childGroup = (VGroup) child;
927                     drawGroupTree(childGroup, currentGroup.mStackedMatrix,
928                             canvas, w, h, filter);
929                 } else if (child instanceof VPath) {
930                     VPath childPath = (VPath) child;
931                     drawPath(currentGroup, childPath, canvas, w, h, filter);
932                 }
933             }
934         }
935 
draw(Canvas canvas, int w, int h, ColorFilter filter)936         public void draw(Canvas canvas, int w, int h, ColorFilter filter) {
937             // Travese the tree in pre-order to draw.
938             drawGroupTree(mRootGroup, IDENTITY_MATRIX, canvas, w, h, filter);
939         }
940 
drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h, ColorFilter filter)941         private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h,
942                 ColorFilter filter) {
943             final float scaleX = w / mViewportWidth;
944             final float scaleY = h / mViewportHeight;
945             final float minScale = Math.min(scaleX, scaleY);
946 
947             mFinalPathMatrix.set(vGroup.mStackedMatrix);
948             mFinalPathMatrix.postScale(scaleX, scaleY);
949 
950             vPath.toPath(mPath);
951             final Path path = mPath;
952 
953             mRenderPath.reset();
954 
955             if (vPath.isClipPath()) {
956                 mRenderPath.addPath(path, mFinalPathMatrix);
957                 canvas.clipPath(mRenderPath, Region.Op.REPLACE);
958             } else {
959                 VFullPath fullPath = (VFullPath) vPath;
960                 if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
961                     float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
962                     float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;
963 
964                     if (mPathMeasure == null) {
965                         mPathMeasure = new PathMeasure();
966                     }
967                     mPathMeasure.setPath(mPath, false);
968 
969                     float len = mPathMeasure.getLength();
970                     start = start * len;
971                     end = end * len;
972                     path.reset();
973                     if (start > end) {
974                         mPathMeasure.getSegment(start, len, path, true);
975                         mPathMeasure.getSegment(0f, end, path, true);
976                     } else {
977                         mPathMeasure.getSegment(start, end, path, true);
978                     }
979                     path.rLineTo(0, 0); // fix bug in measure
980                 }
981                 mRenderPath.addPath(path, mFinalPathMatrix);
982 
983                 if (fullPath.mFillColor != Color.TRANSPARENT) {
984                     if (mFillPaint == null) {
985                         mFillPaint = new Paint();
986                         mFillPaint.setStyle(Paint.Style.FILL);
987                         mFillPaint.setAntiAlias(true);
988                     }
989 
990                     final Paint fillPaint = mFillPaint;
991                     fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha));
992                     fillPaint.setColorFilter(filter);
993                     canvas.drawPath(mRenderPath, fillPaint);
994                 }
995 
996                 if (fullPath.mStrokeColor != Color.TRANSPARENT) {
997                     if (mStrokePaint == null) {
998                         mStrokePaint = new Paint();
999                         mStrokePaint.setStyle(Paint.Style.STROKE);
1000                         mStrokePaint.setAntiAlias(true);
1001                     }
1002 
1003                     final Paint strokePaint = mStrokePaint;
1004                     if (fullPath.mStrokeLineJoin != null) {
1005                         strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
1006                     }
1007 
1008                     if (fullPath.mStrokeLineCap != null) {
1009                         strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
1010                     }
1011 
1012                     strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
1013                     strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha));
1014                     strokePaint.setColorFilter(filter);
1015                     strokePaint.setStrokeWidth(fullPath.mStrokeWidth * minScale);
1016                     canvas.drawPath(mRenderPath, strokePaint);
1017                 }
1018             }
1019         }
1020     }
1021 
1022     private static class VGroup {
1023         // mStackedMatrix is only used temporarily when drawing, it combines all
1024         // the parents' local matrices with the current one.
1025         private final Matrix mStackedMatrix = new Matrix();
1026 
1027         /////////////////////////////////////////////////////
1028         // Variables below need to be copied (deep copy if applicable) for mutation.
1029         final ArrayList<Object> mChildren = new ArrayList<Object>();
1030 
1031         private float mRotate = 0;
1032         private float mPivotX = 0;
1033         private float mPivotY = 0;
1034         private float mScaleX = 1;
1035         private float mScaleY = 1;
1036         private float mTranslateX = 0;
1037         private float mTranslateY = 0;
1038 
1039         // mLocalMatrix is updated based on the update of transformation information,
1040         // either parsed from the XML or by animation.
1041         private final Matrix mLocalMatrix = new Matrix();
1042         private int mChangingConfigurations;
1043         private int[] mThemeAttrs;
1044         private String mGroupName = null;
1045 
VGroup(VGroup copy, ArrayMap<String, Object> targetsMap)1046         public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
1047             mRotate = copy.mRotate;
1048             mPivotX = copy.mPivotX;
1049             mPivotY = copy.mPivotY;
1050             mScaleX = copy.mScaleX;
1051             mScaleY = copy.mScaleY;
1052             mTranslateX = copy.mTranslateX;
1053             mTranslateY = copy.mTranslateY;
1054             mThemeAttrs = copy.mThemeAttrs;
1055             mGroupName = copy.mGroupName;
1056             mChangingConfigurations = copy.mChangingConfigurations;
1057             if (mGroupName != null) {
1058                 targetsMap.put(mGroupName, this);
1059             }
1060 
1061             mLocalMatrix.set(copy.mLocalMatrix);
1062 
1063             final ArrayList<Object> children = copy.mChildren;
1064             for (int i = 0; i < children.size(); i++) {
1065                 Object copyChild = children.get(i);
1066                 if (copyChild instanceof VGroup) {
1067                     VGroup copyGroup = (VGroup) copyChild;
1068                     mChildren.add(new VGroup(copyGroup, targetsMap));
1069                 } else {
1070                     VPath newPath = null;
1071                     if (copyChild instanceof VFullPath) {
1072                         newPath = new VFullPath((VFullPath) copyChild);
1073                     } else if (copyChild instanceof VClipPath) {
1074                         newPath = new VClipPath((VClipPath) copyChild);
1075                     } else {
1076                         throw new IllegalStateException("Unknown object in the tree!");
1077                     }
1078                     mChildren.add(newPath);
1079                     if (newPath.mPathName != null) {
1080                         targetsMap.put(newPath.mPathName, newPath);
1081                     }
1082                 }
1083             }
1084         }
1085 
VGroup()1086         public VGroup() {
1087         }
1088 
getGroupName()1089         public String getGroupName() {
1090             return mGroupName;
1091         }
1092 
getLocalMatrix()1093         public Matrix getLocalMatrix() {
1094             return mLocalMatrix;
1095         }
1096 
inflate(Resources res, AttributeSet attrs, Theme theme)1097         public void inflate(Resources res, AttributeSet attrs, Theme theme) {
1098             final TypedArray a = obtainAttributes(res, theme, attrs,
1099                     R.styleable.VectorDrawableGroup);
1100             updateStateFromTypedArray(a);
1101             a.recycle();
1102         }
1103 
updateStateFromTypedArray(TypedArray a)1104         private void updateStateFromTypedArray(TypedArray a) {
1105             // Account for any configuration changes.
1106             mChangingConfigurations |= a.getChangingConfigurations();
1107 
1108             // Extract the theme attributes, if any.
1109             mThemeAttrs = a.extractThemeAttrs();
1110 
1111             mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate);
1112             mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX);
1113             mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY);
1114             mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX);
1115             mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY);
1116             mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX);
1117             mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
1118 
1119             final String groupName = a.getString(R.styleable.VectorDrawableGroup_name);
1120             if (groupName != null) {
1121                 mGroupName = groupName;
1122             }
1123 
1124             updateLocalMatrix();
1125         }
1126 
canApplyTheme()1127         public boolean canApplyTheme() {
1128             return mThemeAttrs != null;
1129         }
1130 
applyTheme(Theme t)1131         public void applyTheme(Theme t) {
1132             if (mThemeAttrs == null) {
1133                 return;
1134             }
1135 
1136             final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawableGroup);
1137             updateStateFromTypedArray(a);
1138             a.recycle();
1139         }
1140 
updateLocalMatrix()1141         private void updateLocalMatrix() {
1142             // The order we apply is the same as the
1143             // RenderNode.cpp::applyViewPropertyTransforms().
1144             mLocalMatrix.reset();
1145             mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
1146             mLocalMatrix.postScale(mScaleX, mScaleY);
1147             mLocalMatrix.postRotate(mRotate, 0, 0);
1148             mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
1149         }
1150 
1151         /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1152         @SuppressWarnings("unused")
getRotation()1153         public float getRotation() {
1154             return mRotate;
1155         }
1156 
1157         @SuppressWarnings("unused")
setRotation(float rotation)1158         public void setRotation(float rotation) {
1159             if (rotation != mRotate) {
1160                 mRotate = rotation;
1161                 updateLocalMatrix();
1162             }
1163         }
1164 
1165         @SuppressWarnings("unused")
getPivotX()1166         public float getPivotX() {
1167             return mPivotX;
1168         }
1169 
1170         @SuppressWarnings("unused")
setPivotX(float pivotX)1171         public void setPivotX(float pivotX) {
1172             if (pivotX != mPivotX) {
1173                 mPivotX = pivotX;
1174                 updateLocalMatrix();
1175             }
1176         }
1177 
1178         @SuppressWarnings("unused")
getPivotY()1179         public float getPivotY() {
1180             return mPivotY;
1181         }
1182 
1183         @SuppressWarnings("unused")
setPivotY(float pivotY)1184         public void setPivotY(float pivotY) {
1185             if (pivotY != mPivotY) {
1186                 mPivotY = pivotY;
1187                 updateLocalMatrix();
1188             }
1189         }
1190 
1191         @SuppressWarnings("unused")
getScaleX()1192         public float getScaleX() {
1193             return mScaleX;
1194         }
1195 
1196         @SuppressWarnings("unused")
setScaleX(float scaleX)1197         public void setScaleX(float scaleX) {
1198             if (scaleX != mScaleX) {
1199                 mScaleX = scaleX;
1200                 updateLocalMatrix();
1201             }
1202         }
1203 
1204         @SuppressWarnings("unused")
getScaleY()1205         public float getScaleY() {
1206             return mScaleY;
1207         }
1208 
1209         @SuppressWarnings("unused")
setScaleY(float scaleY)1210         public void setScaleY(float scaleY) {
1211             if (scaleY != mScaleY) {
1212                 mScaleY = scaleY;
1213                 updateLocalMatrix();
1214             }
1215         }
1216 
1217         @SuppressWarnings("unused")
getTranslateX()1218         public float getTranslateX() {
1219             return mTranslateX;
1220         }
1221 
1222         @SuppressWarnings("unused")
setTranslateX(float translateX)1223         public void setTranslateX(float translateX) {
1224             if (translateX != mTranslateX) {
1225                 mTranslateX = translateX;
1226                 updateLocalMatrix();
1227             }
1228         }
1229 
1230         @SuppressWarnings("unused")
getTranslateY()1231         public float getTranslateY() {
1232             return mTranslateY;
1233         }
1234 
1235         @SuppressWarnings("unused")
setTranslateY(float translateY)1236         public void setTranslateY(float translateY) {
1237             if (translateY != mTranslateY) {
1238                 mTranslateY = translateY;
1239                 updateLocalMatrix();
1240             }
1241         }
1242     }
1243 
1244     /**
1245      * Common Path information for clip path and normal path.
1246      */
1247     private static class VPath {
1248         protected PathParser.PathDataNode[] mNodes = null;
1249         String mPathName;
1250         int mChangingConfigurations;
1251 
VPath()1252         public VPath() {
1253             // Empty constructor.
1254         }
1255 
VPath(VPath copy)1256         public VPath(VPath copy) {
1257             mPathName = copy.mPathName;
1258             mChangingConfigurations = copy.mChangingConfigurations;
1259             mNodes = PathParser.deepCopyNodes(copy.mNodes);
1260         }
1261 
toPath(Path path)1262         public void toPath(Path path) {
1263             path.reset();
1264             if (mNodes != null) {
1265                 PathParser.PathDataNode.nodesToPath(mNodes, path);
1266             }
1267         }
1268 
getPathName()1269         public String getPathName() {
1270             return mPathName;
1271         }
1272 
canApplyTheme()1273         public boolean canApplyTheme() {
1274             return false;
1275         }
1276 
applyTheme(Theme t)1277         public void applyTheme(Theme t) {
1278         }
1279 
isClipPath()1280         public boolean isClipPath() {
1281             return false;
1282         }
1283 
1284         /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1285         @SuppressWarnings("unused")
getPathData()1286         public PathParser.PathDataNode[] getPathData() {
1287             return mNodes;
1288         }
1289 
1290         @SuppressWarnings("unused")
setPathData(PathParser.PathDataNode[] nodes)1291         public void setPathData(PathParser.PathDataNode[] nodes) {
1292             if (!PathParser.canMorph(mNodes, nodes)) {
1293                 // This should not happen in the middle of animation.
1294                 mNodes = PathParser.deepCopyNodes(nodes);
1295             } else {
1296                 PathParser.updateNodes(mNodes, nodes);
1297             }
1298         }
1299     }
1300 
1301     /**
1302      * Clip path, which only has name and pathData.
1303      */
1304     private static class VClipPath extends VPath {
VClipPath()1305         public VClipPath() {
1306             // Empty constructor.
1307         }
1308 
VClipPath(VClipPath copy)1309         public VClipPath(VClipPath copy) {
1310             super(copy);
1311         }
1312 
inflate(Resources r, AttributeSet attrs, Theme theme)1313         public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1314             final TypedArray a = obtainAttributes(r, theme, attrs,
1315                     R.styleable.VectorDrawableClipPath);
1316             updateStateFromTypedArray(a);
1317             a.recycle();
1318         }
1319 
updateStateFromTypedArray(TypedArray a)1320         private void updateStateFromTypedArray(TypedArray a) {
1321             // Account for any configuration changes.
1322             mChangingConfigurations |= a.getChangingConfigurations();
1323 
1324             final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name);
1325             if (pathName != null) {
1326                 mPathName = pathName;
1327             }
1328 
1329             final String pathData = a.getString(R.styleable.VectorDrawableClipPath_pathData);
1330             if (pathData != null) {
1331                 mNodes = PathParser.createNodesFromPathData(pathData);
1332             }
1333         }
1334 
1335         @Override
isClipPath()1336         public boolean isClipPath() {
1337             return true;
1338         }
1339     }
1340 
1341     /**
1342      * Normal path, which contains all the fill / paint information.
1343      */
1344     private static class VFullPath extends VPath {
1345         /////////////////////////////////////////////////////
1346         // Variables below need to be copied (deep copy if applicable) for mutation.
1347         private int[] mThemeAttrs;
1348 
1349         int mStrokeColor = Color.TRANSPARENT;
1350         float mStrokeWidth = 0;
1351 
1352         int mFillColor = Color.TRANSPARENT;
1353         float mStrokeAlpha = 1.0f;
1354         int mFillRule;
1355         float mFillAlpha = 1.0f;
1356         float mTrimPathStart = 0;
1357         float mTrimPathEnd = 1;
1358         float mTrimPathOffset = 0;
1359 
1360         Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
1361         Paint.Join mStrokeLineJoin = Paint.Join.MITER;
1362         float mStrokeMiterlimit = 4;
1363 
VFullPath()1364         public VFullPath() {
1365             // Empty constructor.
1366         }
1367 
VFullPath(VFullPath copy)1368         public VFullPath(VFullPath copy) {
1369             super(copy);
1370             mThemeAttrs = copy.mThemeAttrs;
1371 
1372             mStrokeColor = copy.mStrokeColor;
1373             mStrokeWidth = copy.mStrokeWidth;
1374             mStrokeAlpha = copy.mStrokeAlpha;
1375             mFillColor = copy.mFillColor;
1376             mFillRule = copy.mFillRule;
1377             mFillAlpha = copy.mFillAlpha;
1378             mTrimPathStart = copy.mTrimPathStart;
1379             mTrimPathEnd = copy.mTrimPathEnd;
1380             mTrimPathOffset = copy.mTrimPathOffset;
1381 
1382             mStrokeLineCap = copy.mStrokeLineCap;
1383             mStrokeLineJoin = copy.mStrokeLineJoin;
1384             mStrokeMiterlimit = copy.mStrokeMiterlimit;
1385         }
1386 
getStrokeLineCap(int id, Paint.Cap defValue)1387         private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
1388             switch (id) {
1389                 case LINECAP_BUTT:
1390                     return Paint.Cap.BUTT;
1391                 case LINECAP_ROUND:
1392                     return Paint.Cap.ROUND;
1393                 case LINECAP_SQUARE:
1394                     return Paint.Cap.SQUARE;
1395                 default:
1396                     return defValue;
1397             }
1398         }
1399 
getStrokeLineJoin(int id, Paint.Join defValue)1400         private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) {
1401             switch (id) {
1402                 case LINEJOIN_MITER:
1403                     return Paint.Join.MITER;
1404                 case LINEJOIN_ROUND:
1405                     return Paint.Join.ROUND;
1406                 case LINEJOIN_BEVEL:
1407                     return Paint.Join.BEVEL;
1408                 default:
1409                     return defValue;
1410             }
1411         }
1412 
1413         @Override
canApplyTheme()1414         public boolean canApplyTheme() {
1415             return mThemeAttrs != null;
1416         }
1417 
inflate(Resources r, AttributeSet attrs, Theme theme)1418         public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1419             final TypedArray a = obtainAttributes(r, theme, attrs,
1420                     R.styleable.VectorDrawablePath);
1421             updateStateFromTypedArray(a);
1422             a.recycle();
1423         }
1424 
updateStateFromTypedArray(TypedArray a)1425         private void updateStateFromTypedArray(TypedArray a) {
1426             // Account for any configuration changes.
1427             mChangingConfigurations |= a.getChangingConfigurations();
1428 
1429             // Extract the theme attributes, if any.
1430             mThemeAttrs = a.extractThemeAttrs();
1431 
1432             final String pathName = a.getString(R.styleable.VectorDrawablePath_name);
1433             if (pathName != null) {
1434                 mPathName = pathName;
1435             }
1436 
1437             final String pathData = a.getString(R.styleable.VectorDrawablePath_pathData);
1438             if (pathData != null) {
1439                 mNodes = PathParser.createNodesFromPathData(pathData);
1440             }
1441 
1442             mFillColor = a.getColor(R.styleable.VectorDrawablePath_fillColor,
1443                     mFillColor);
1444             mFillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha,
1445                     mFillAlpha);
1446             mStrokeLineCap = getStrokeLineCap(a.getInt(
1447                     R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
1448             mStrokeLineJoin = getStrokeLineJoin(a.getInt(
1449                     R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
1450             mStrokeMiterlimit = a.getFloat(
1451                     R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
1452             mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_strokeColor,
1453                     mStrokeColor);
1454             mStrokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha,
1455                     mStrokeAlpha);
1456             mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
1457                     mStrokeWidth);
1458             mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
1459                     mTrimPathEnd);
1460             mTrimPathOffset = a.getFloat(
1461                     R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
1462             mTrimPathStart = a.getFloat(
1463                     R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
1464         }
1465 
1466         @Override
applyTheme(Theme t)1467         public void applyTheme(Theme t) {
1468             if (mThemeAttrs == null) {
1469                 return;
1470             }
1471 
1472             final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath);
1473             updateStateFromTypedArray(a);
1474             a.recycle();
1475         }
1476 
1477         /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1478         @SuppressWarnings("unused")
getStrokeColor()1479         int getStrokeColor() {
1480             return mStrokeColor;
1481         }
1482 
1483         @SuppressWarnings("unused")
setStrokeColor(int strokeColor)1484         void setStrokeColor(int strokeColor) {
1485             mStrokeColor = strokeColor;
1486         }
1487 
1488         @SuppressWarnings("unused")
getStrokeWidth()1489         float getStrokeWidth() {
1490             return mStrokeWidth;
1491         }
1492 
1493         @SuppressWarnings("unused")
setStrokeWidth(float strokeWidth)1494         void setStrokeWidth(float strokeWidth) {
1495             mStrokeWidth = strokeWidth;
1496         }
1497 
1498         @SuppressWarnings("unused")
getStrokeAlpha()1499         float getStrokeAlpha() {
1500             return mStrokeAlpha;
1501         }
1502 
1503         @SuppressWarnings("unused")
setStrokeAlpha(float strokeAlpha)1504         void setStrokeAlpha(float strokeAlpha) {
1505             mStrokeAlpha = strokeAlpha;
1506         }
1507 
1508         @SuppressWarnings("unused")
getFillColor()1509         int getFillColor() {
1510             return mFillColor;
1511         }
1512 
1513         @SuppressWarnings("unused")
setFillColor(int fillColor)1514         void setFillColor(int fillColor) {
1515             mFillColor = fillColor;
1516         }
1517 
1518         @SuppressWarnings("unused")
getFillAlpha()1519         float getFillAlpha() {
1520             return mFillAlpha;
1521         }
1522 
1523         @SuppressWarnings("unused")
setFillAlpha(float fillAlpha)1524         void setFillAlpha(float fillAlpha) {
1525             mFillAlpha = fillAlpha;
1526         }
1527 
1528         @SuppressWarnings("unused")
getTrimPathStart()1529         float getTrimPathStart() {
1530             return mTrimPathStart;
1531         }
1532 
1533         @SuppressWarnings("unused")
setTrimPathStart(float trimPathStart)1534         void setTrimPathStart(float trimPathStart) {
1535             mTrimPathStart = trimPathStart;
1536         }
1537 
1538         @SuppressWarnings("unused")
getTrimPathEnd()1539         float getTrimPathEnd() {
1540             return mTrimPathEnd;
1541         }
1542 
1543         @SuppressWarnings("unused")
setTrimPathEnd(float trimPathEnd)1544         void setTrimPathEnd(float trimPathEnd) {
1545             mTrimPathEnd = trimPathEnd;
1546         }
1547 
1548         @SuppressWarnings("unused")
getTrimPathOffset()1549         float getTrimPathOffset() {
1550             return mTrimPathOffset;
1551         }
1552 
1553         @SuppressWarnings("unused")
setTrimPathOffset(float trimPathOffset)1554         void setTrimPathOffset(float trimPathOffset) {
1555             mTrimPathOffset = trimPathOffset;
1556         }
1557     }
1558 }
1559