• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.support.graphics.drawable;
16 
17 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
18 
19 import android.annotation.SuppressLint;
20 import android.content.res.ColorStateList;
21 import android.content.res.Resources;
22 import android.content.res.Resources.Theme;
23 import android.content.res.TypedArray;
24 import android.graphics.Bitmap;
25 import android.graphics.Canvas;
26 import android.graphics.Color;
27 import android.graphics.ColorFilter;
28 import android.graphics.Matrix;
29 import android.graphics.Paint;
30 import android.graphics.Path;
31 import android.graphics.PathMeasure;
32 import android.graphics.PixelFormat;
33 import android.graphics.PorterDuff;
34 import android.graphics.PorterDuff.Mode;
35 import android.graphics.PorterDuffColorFilter;
36 import android.graphics.Rect;
37 import android.graphics.drawable.Drawable;
38 import android.graphics.drawable.VectorDrawable;
39 import android.os.Build;
40 import android.support.annotation.DrawableRes;
41 import android.support.annotation.NonNull;
42 import android.support.annotation.Nullable;
43 import android.support.annotation.RequiresApi;
44 import android.support.annotation.RestrictTo;
45 import android.support.v4.content.res.ResourcesCompat;
46 import android.support.v4.content.res.TypedArrayUtils;
47 import android.support.v4.graphics.PathParser;
48 import android.support.v4.graphics.drawable.DrawableCompat;
49 import android.support.v4.util.ArrayMap;
50 import android.support.v4.view.ViewCompat;
51 import android.util.AttributeSet;
52 import android.util.Log;
53 import android.util.Xml;
54 
55 import org.xmlpull.v1.XmlPullParser;
56 import org.xmlpull.v1.XmlPullParserException;
57 
58 import java.io.IOException;
59 import java.util.ArrayList;
60 import java.util.Stack;
61 
62 /**
63  * For API 24 and above, this class is delegating to the framework's {@link VectorDrawable}.
64  * For older API version, this class lets you create a drawable based on an XML vector graphic.
65  * <p/>
66  * You can always create a VectorDrawableCompat object and use it as a Drawable by the Java API.
67  * In order to refer to VectorDrawableCompat inside a XML file,  you can use app:srcCompat attribute
68  * in AppCompat library's ImageButton or ImageView.
69  * <p/>
70  * <strong>Note:</strong> To optimize for the re-drawing performance, one bitmap cache is created
71  * for each VectorDrawableCompat. Therefore, referring to the same VectorDrawableCompat means
72  * sharing the same bitmap cache. If these references don't agree upon on the same size, the bitmap
73  * will be recreated and redrawn every time size is changed. In other words, if a VectorDrawable is
74  * used for different sizes, it is more efficient to create multiple VectorDrawables, one for each
75  * size.
76  * <p/>
77  * VectorDrawableCompat can be defined in an XML file with the <code>&lt;vector></code> element.
78  * <p/>
79  * The VectorDrawableCompat has the following elements:
80  * <p/>
81  * <dt><code>&lt;vector></code></dt>
82  * <dl>
83  * <dd>Used to define a vector drawable
84  * <dl>
85  * <dt><code>android:name</code></dt>
86  * <dd>Defines the name of this vector drawable.</dd>
87  * <dt><code>android:width</code></dt>
88  * <dd>Used to define the intrinsic width of the drawable.
89  * This support all the dimension units, normally specified with dp.</dd>
90  * <dt><code>android:height</code></dt>
91  * <dd>Used to define the intrinsic height the drawable.
92  * This support all the dimension units, normally specified with dp.</dd>
93  * <dt><code>android:viewportWidth</code></dt>
94  * <dd>Used to define the width of the viewport space. Viewport is basically
95  * the virtual canvas where the paths are drawn on.</dd>
96  * <dt><code>android:viewportHeight</code></dt>
97  * <dd>Used to define the height of the viewport space. Viewport is basically
98  * the virtual canvas where the paths are drawn on.</dd>
99  * <dt><code>android:tint</code></dt>
100  * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd>
101  * <dt><code>android:tintMode</code></dt>
102  * <dd>The Porter-Duff blending mode for the tint color. Default is src_in.</dd>
103  * <dt><code>android:autoMirrored</code></dt>
104  * <dd>Indicates if the drawable needs to be mirrored when its layout direction is
105  * RTL (right-to-left). Default is false.</dd>
106  * <dt><code>android:alpha</code></dt>
107  * <dd>The opacity of this drawable. Default is 1.</dd>
108  * </dl></dd>
109  * </dl>
110  *
111  * <dl>
112  * <dt><code>&lt;group></code></dt>
113  * <dd>Defines a group of paths or subgroups, plus transformation information.
114  * The transformations are defined in the same coordinates as the viewport.
115  * And the transformations are applied in the order of scale, rotate then translate.
116  * <dl>
117  * <dt><code>android:name</code></dt>
118  * <dd>Defines the name of the group.</dd>
119  * <dt><code>android:rotation</code></dt>
120  * <dd>The degrees of rotation of the group. Default is 0.</dd>
121  * <dt><code>android:pivotX</code></dt>
122  * <dd>The X coordinate of the pivot for the scale and rotation of the group.
123  * This is defined in the viewport space. Default is 0.</dd>
124  * <dt><code>android:pivotY</code></dt>
125  * <dd>The Y coordinate of the pivot for the scale and rotation of the group.
126  * This is defined in the viewport space. Default is 0.</dd>
127  * <dt><code>android:scaleX</code></dt>
128  * <dd>The amount of scale on the X Coordinate. Default is 1.</dd>
129  * <dt><code>android:scaleY</code></dt>
130  * <dd>The amount of scale on the Y coordinate. Default is 1.</dd>
131  * <dt><code>android:translateX</code></dt>
132  * <dd>The amount of translation on the X coordinate.
133  * This is defined in the viewport space. Default is 0.</dd>
134  * <dt><code>android:translateY</code></dt>
135  * <dd>The amount of translation on the Y coordinate.
136  * This is defined in the viewport space. Default is 0.</dd>
137  * </dl></dd>
138  * </dl>
139  *
140  * <dl>
141  * <dt><code>&lt;path></code></dt>
142  * <dd>Defines paths to be drawn.
143  * <dl>
144  * <dt><code>android:name</code></dt>
145  * <dd>Defines the name of the path.</dd>
146  * <dt><code>android:pathData</code></dt>
147  * <dd>Defines path data using exactly same format as "d" attribute
148  * in the SVG's path data. This is defined in the viewport space.</dd>
149  * <dt><code>android:fillColor</code></dt>
150  * <dd>Specifies the color used to fill the path.
151  * If this property is animated, any value set by the animation will override the original value.
152  * No path fill is drawn if this property is not specified.</dd>
153  * <dt><code>android:strokeColor</code></dt>
154  * <dd>Specifies the color used to draw the path outline.
155  * If this property is animated, any value set by the animation will override the original value.
156  * No path outline is drawn if this property is not specified.</dd>
157  * <dt><code>android:strokeWidth</code></dt>
158  * <dd>The width a path stroke. Default is 0.</dd>
159  * <dt><code>android:strokeAlpha</code></dt>
160  * <dd>The opacity of a path stroke. Default is 1.</dd>
161  * <dt><code>android:fillAlpha</code></dt>
162  * <dd>The opacity to fill the path with. Default is 1.</dd>
163  * <dt><code>android:trimPathStart</code></dt>
164  * <dd>The fraction of the path to trim from the start, in the range from 0 to 1. Default is 0.</dd>
165  * <dt><code>android:trimPathEnd</code></dt>
166  * <dd>The fraction of the path to trim from the end, in the range from 0 to 1. Default is 1.</dd>
167  * <dt><code>android:trimPathOffset</code></dt>
168  * <dd>Shift trim region (allows showed region to include the start and end), in the range
169  * from 0 to 1. Default is 0.</dd>
170  * <dt><code>android:strokeLineCap</code></dt>
171  * <dd>Sets the linecap for a stroked path: butt, round, square. Default is butt.</dd>
172  * <dt><code>android:strokeLineJoin</code></dt>
173  * <dd>Sets the lineJoin for a stroked path: miter,round,bevel. Default is miter.</dd>
174  * <dt><code>android:strokeMiterLimit</code></dt>
175  * <dd>Sets the Miter limit for a stroked path. Default is 4.</dd>
176  * </dl></dd>
177  * </dl>
178  *
179  * <dl>
180  * <dt><code>&lt;clip-path></code></dt>
181  * <dd>Defines path to be the current clip. Note that the clip path only apply to
182  * the current group and its children.
183  * <dl>
184  * <dt><code>android:name</code></dt>
185  * <dd>Defines the name of the clip path.</dd>
186  * <dt><code>android:pathData</code></dt>
187  * <dd>Defines clip path using the same format as "d" attribute
188  * in the SVG's path data.</dd>
189  * </dl></dd>
190  * </dl>
191  * <p/>
192  * Note that theme attributes in XML file are supported through
193  * <code>{@link #inflate(Resources, XmlPullParser, AttributeSet, Theme)}</code>.
194  */
195 public class VectorDrawableCompat extends VectorDrawableCommon {
196     static final String LOGTAG = "VectorDrawableCompat";
197 
198     static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;
199 
200     private static final String SHAPE_CLIP_PATH = "clip-path";
201     private static final String SHAPE_GROUP = "group";
202     private static final String SHAPE_PATH = "path";
203     private static final String SHAPE_VECTOR = "vector";
204 
205     private static final int LINECAP_BUTT = 0;
206     private static final int LINECAP_ROUND = 1;
207     private static final int LINECAP_SQUARE = 2;
208 
209     private static final int LINEJOIN_MITER = 0;
210     private static final int LINEJOIN_ROUND = 1;
211     private static final int LINEJOIN_BEVEL = 2;
212 
213     // Cap the bitmap size, such that it won't hurt the performance too much
214     // and it won't crash due to a very large scale.
215     // The drawable will look blurry above this size.
216     private static final int MAX_CACHED_BITMAP_SIZE = 2048;
217 
218     private static final boolean DBG_VECTOR_DRAWABLE = false;
219 
220     private VectorDrawableCompatState mVectorState;
221 
222     private PorterDuffColorFilter mTintFilter;
223     private ColorFilter mColorFilter;
224 
225     private boolean mMutated;
226 
227     // AnimatedVectorDrawable needs to turn off the cache all the time, otherwise,
228     // caching the bitmap by default is allowed.
229     private boolean mAllowCaching = true;
230 
231     // The Constant state associated with the <code>mDelegateDrawable</code>.
232     private ConstantState mCachedConstantStateDelegate;
233 
234     // Temp variable, only for saving "new" operation at the draw() time.
235     private final float[] mTmpFloats = new float[9];
236     private final Matrix mTmpMatrix = new Matrix();
237     private final Rect mTmpBounds = new Rect();
238 
VectorDrawableCompat()239     VectorDrawableCompat() {
240         mVectorState = new VectorDrawableCompatState();
241     }
242 
VectorDrawableCompat(@onNull VectorDrawableCompatState state)243     VectorDrawableCompat(@NonNull VectorDrawableCompatState state) {
244         mVectorState = state;
245         mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
246     }
247 
248     @Override
mutate()249     public Drawable mutate() {
250         if (mDelegateDrawable != null) {
251             mDelegateDrawable.mutate();
252             return this;
253         }
254 
255         if (!mMutated && super.mutate() == this) {
256             mVectorState = new VectorDrawableCompatState(mVectorState);
257             mMutated = true;
258         }
259         return this;
260     }
261 
getTargetByName(String name)262     Object getTargetByName(String name) {
263         return mVectorState.mVPathRenderer.mVGTargetsMap.get(name);
264     }
265 
266     @Override
getConstantState()267     public ConstantState getConstantState() {
268         if (mDelegateDrawable != null && Build.VERSION.SDK_INT >= 24) {
269             // Such that the configuration can be refreshed.
270             return new VectorDrawableDelegateState(mDelegateDrawable.getConstantState());
271         }
272         mVectorState.mChangingConfigurations = getChangingConfigurations();
273         return mVectorState;
274     }
275 
276     @Override
draw(Canvas canvas)277     public void draw(Canvas canvas) {
278         if (mDelegateDrawable != null) {
279             mDelegateDrawable.draw(canvas);
280             return;
281         }
282         // We will offset the bounds for drawBitmap, so copyBounds() here instead
283         // of getBounds().
284         copyBounds(mTmpBounds);
285         if (mTmpBounds.width() <= 0 || mTmpBounds.height() <= 0) {
286             // Nothing to draw
287             return;
288         }
289 
290         // Color filters always override tint filters.
291         final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter);
292 
293         // The imageView can scale the canvas in different ways, in order to
294         // avoid blurry scaling, we have to draw into a bitmap with exact pixel
295         // size first. This bitmap size is determined by the bounds and the
296         // canvas scale.
297         canvas.getMatrix(mTmpMatrix);
298         mTmpMatrix.getValues(mTmpFloats);
299         float canvasScaleX = Math.abs(mTmpFloats[Matrix.MSCALE_X]);
300         float canvasScaleY = Math.abs(mTmpFloats[Matrix.MSCALE_Y]);
301 
302         float canvasSkewX = Math.abs(mTmpFloats[Matrix.MSKEW_X]);
303         float canvasSkewY = Math.abs(mTmpFloats[Matrix.MSKEW_Y]);
304 
305         // When there is any rotation / skew, then the scale value is not valid.
306         if (canvasSkewX != 0 || canvasSkewY != 0) {
307             canvasScaleX = 1.0f;
308             canvasScaleY = 1.0f;
309         }
310 
311         int scaledWidth = (int) (mTmpBounds.width() * canvasScaleX);
312         int scaledHeight = (int) (mTmpBounds.height() * canvasScaleY);
313         scaledWidth = Math.min(MAX_CACHED_BITMAP_SIZE, scaledWidth);
314         scaledHeight = Math.min(MAX_CACHED_BITMAP_SIZE, scaledHeight);
315 
316         if (scaledWidth <= 0 || scaledHeight <= 0) {
317             return;
318         }
319 
320         final int saveCount = canvas.save();
321         canvas.translate(mTmpBounds.left, mTmpBounds.top);
322 
323         // Handle RTL mirroring.
324         final boolean needMirroring = needMirroring();
325         if (needMirroring) {
326             canvas.translate(mTmpBounds.width(), 0);
327             canvas.scale(-1.0f, 1.0f);
328         }
329 
330         // At this point, canvas has been translated to the right position.
331         // And we use this bound for the destination rect for the drawBitmap, so
332         // we offset to (0, 0);
333         mTmpBounds.offsetTo(0, 0);
334 
335         mVectorState.createCachedBitmapIfNeeded(scaledWidth, scaledHeight);
336         if (!mAllowCaching) {
337             mVectorState.updateCachedBitmap(scaledWidth, scaledHeight);
338         } else {
339             if (!mVectorState.canReuseCache()) {
340                 mVectorState.updateCachedBitmap(scaledWidth, scaledHeight);
341                 mVectorState.updateCacheStates();
342             }
343         }
344         mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter, mTmpBounds);
345         canvas.restoreToCount(saveCount);
346     }
347 
348     @Override
getAlpha()349     public int getAlpha() {
350         if (mDelegateDrawable != null) {
351             return DrawableCompat.getAlpha(mDelegateDrawable);
352         }
353 
354         return mVectorState.mVPathRenderer.getRootAlpha();
355     }
356 
357     @Override
setAlpha(int alpha)358     public void setAlpha(int alpha) {
359         if (mDelegateDrawable != null) {
360             mDelegateDrawable.setAlpha(alpha);
361             return;
362         }
363 
364         if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) {
365             mVectorState.mVPathRenderer.setRootAlpha(alpha);
366             invalidateSelf();
367         }
368     }
369 
370     @Override
setColorFilter(ColorFilter colorFilter)371     public void setColorFilter(ColorFilter colorFilter) {
372         if (mDelegateDrawable != null) {
373             mDelegateDrawable.setColorFilter(colorFilter);
374             return;
375         }
376 
377         mColorFilter = colorFilter;
378         invalidateSelf();
379     }
380 
381     /**
382      * Ensures the tint filter is consistent with the current tint color and
383      * mode.
384      */
updateTintFilter(PorterDuffColorFilter tintFilter, ColorStateList tint, PorterDuff.Mode tintMode)385     PorterDuffColorFilter updateTintFilter(PorterDuffColorFilter tintFilter, ColorStateList tint,
386                                            PorterDuff.Mode tintMode) {
387         if (tint == null || tintMode == null) {
388             return null;
389         }
390         // setMode, setColor of PorterDuffColorFilter are not public method in SDK v7.
391         // Therefore we create a new one all the time here. Don't expect this is called often.
392         final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
393         return new PorterDuffColorFilter(color, tintMode);
394     }
395 
396     @Override
setTint(int tint)397     public void setTint(int tint) {
398         if (mDelegateDrawable != null) {
399             DrawableCompat.setTint(mDelegateDrawable, tint);
400             return;
401         }
402 
403         setTintList(ColorStateList.valueOf(tint));
404     }
405 
406     @Override
setTintList(ColorStateList tint)407     public void setTintList(ColorStateList tint) {
408         if (mDelegateDrawable != null) {
409             DrawableCompat.setTintList(mDelegateDrawable, tint);
410             return;
411         }
412 
413         final VectorDrawableCompatState state = mVectorState;
414         if (state.mTint != tint) {
415             state.mTint = tint;
416             mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode);
417             invalidateSelf();
418         }
419     }
420 
421     @Override
setTintMode(Mode tintMode)422     public void setTintMode(Mode tintMode) {
423         if (mDelegateDrawable != null) {
424             DrawableCompat.setTintMode(mDelegateDrawable, tintMode);
425             return;
426         }
427 
428         final VectorDrawableCompatState state = mVectorState;
429         if (state.mTintMode != tintMode) {
430             state.mTintMode = tintMode;
431             mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode);
432             invalidateSelf();
433         }
434     }
435 
436     @Override
isStateful()437     public boolean isStateful() {
438         if (mDelegateDrawable != null) {
439             return mDelegateDrawable.isStateful();
440         }
441 
442         return super.isStateful() || (mVectorState != null && mVectorState.mTint != null
443                 && mVectorState.mTint.isStateful());
444     }
445 
446     @Override
onStateChange(int[] stateSet)447     protected boolean onStateChange(int[] stateSet) {
448         if (mDelegateDrawable != null) {
449             return mDelegateDrawable.setState(stateSet);
450         }
451 
452         final VectorDrawableCompatState state = mVectorState;
453         if (state.mTint != null && state.mTintMode != null) {
454             mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
455             invalidateSelf();
456             return true;
457         }
458         return false;
459     }
460 
461     @Override
getOpacity()462     public int getOpacity() {
463         if (mDelegateDrawable != null) {
464             return mDelegateDrawable.getOpacity();
465         }
466 
467         return PixelFormat.TRANSLUCENT;
468     }
469 
470     @Override
getIntrinsicWidth()471     public int getIntrinsicWidth() {
472         if (mDelegateDrawable != null) {
473             return mDelegateDrawable.getIntrinsicWidth();
474         }
475 
476         return (int) mVectorState.mVPathRenderer.mBaseWidth;
477     }
478 
479     @Override
getIntrinsicHeight()480     public int getIntrinsicHeight() {
481         if (mDelegateDrawable != null) {
482             return mDelegateDrawable.getIntrinsicHeight();
483         }
484 
485         return (int) mVectorState.mVPathRenderer.mBaseHeight;
486     }
487 
488     // Don't support re-applying themes. The initial theme loading is working.
489     @Override
canApplyTheme()490     public boolean canApplyTheme() {
491         if (mDelegateDrawable != null) {
492             DrawableCompat.canApplyTheme(mDelegateDrawable);
493         }
494 
495         return false;
496     }
497 
498     @Override
isAutoMirrored()499     public boolean isAutoMirrored() {
500         if (mDelegateDrawable != null) {
501             return DrawableCompat.isAutoMirrored(mDelegateDrawable);
502         }
503         return mVectorState.mAutoMirrored;
504     }
505 
506     @Override
setAutoMirrored(boolean mirrored)507     public void setAutoMirrored(boolean mirrored) {
508         if (mDelegateDrawable != null) {
509             DrawableCompat.setAutoMirrored(mDelegateDrawable, mirrored);
510             return;
511         }
512         mVectorState.mAutoMirrored = mirrored;
513     }
514     /**
515      * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension. This
516      * is used to calculate the path animation accuracy.
517      *
518      * @hide
519      */
520     @RestrictTo(LIBRARY_GROUP)
getPixelSize()521     public float getPixelSize() {
522         if (mVectorState == null || mVectorState.mVPathRenderer == null
523                 || mVectorState.mVPathRenderer.mBaseWidth == 0
524                 || mVectorState.mVPathRenderer.mBaseHeight == 0
525                 || mVectorState.mVPathRenderer.mViewportHeight == 0
526                 || mVectorState.mVPathRenderer.mViewportWidth == 0) {
527             return 1; // fall back to 1:1 pixel mapping.
528         }
529         float intrinsicWidth = mVectorState.mVPathRenderer.mBaseWidth;
530         float intrinsicHeight = mVectorState.mVPathRenderer.mBaseHeight;
531         float viewportWidth = mVectorState.mVPathRenderer.mViewportWidth;
532         float viewportHeight = mVectorState.mVPathRenderer.mViewportHeight;
533         float scaleX = viewportWidth / intrinsicWidth;
534         float scaleY = viewportHeight / intrinsicHeight;
535         return Math.min(scaleX, scaleY);
536     }
537 
538     /**
539      * Create a VectorDrawableCompat object.
540      *
541      * @param res   the resources.
542      * @param resId the resource ID for VectorDrawableCompat object.
543      * @param theme the theme of this vector drawable, it can be null.
544      * @return a new VectorDrawableCompat or null if parsing error is found.
545      */
546     @Nullable
create(@onNull Resources res, @DrawableRes int resId, @Nullable Theme theme)547     public static VectorDrawableCompat create(@NonNull Resources res, @DrawableRes int resId,
548                                               @Nullable Theme theme) {
549         if (Build.VERSION.SDK_INT >= 24) {
550             final VectorDrawableCompat drawable = new VectorDrawableCompat();
551             drawable.mDelegateDrawable = ResourcesCompat.getDrawable(res, resId, theme);
552             drawable.mCachedConstantStateDelegate = new VectorDrawableDelegateState(
553                     drawable.mDelegateDrawable.getConstantState());
554             return drawable;
555         }
556 
557         try {
558             @SuppressLint("ResourceType") final XmlPullParser parser = res.getXml(resId);
559             final AttributeSet attrs = Xml.asAttributeSet(parser);
560             int type;
561             while ((type = parser.next()) != XmlPullParser.START_TAG &&
562                     type != XmlPullParser.END_DOCUMENT) {
563                 // Empty loop
564             }
565             if (type != XmlPullParser.START_TAG) {
566                 throw new XmlPullParserException("No start tag found");
567             }
568             return createFromXmlInner(res, parser, attrs, theme);
569         } catch (XmlPullParserException e) {
570             Log.e(LOGTAG, "parser error", e);
571         } catch (IOException e) {
572             Log.e(LOGTAG, "parser error", e);
573         }
574         return null;
575     }
576 
577     /**
578      * Create a VectorDrawableCompat from inside an XML document using an optional
579      * {@link Theme}. Called on a parser positioned at a tag in an XML
580      * document, tries to create a Drawable from that tag. Returns {@code null}
581      * if the tag is not a valid drawable.
582      */
createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)583     public static VectorDrawableCompat createFromXmlInner(Resources r, XmlPullParser parser,
584             AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException {
585         final VectorDrawableCompat drawable = new VectorDrawableCompat();
586         drawable.inflate(r, parser, attrs, theme);
587         return drawable;
588     }
589 
applyAlpha(int color, float alpha)590     static int applyAlpha(int color, float alpha) {
591         int alphaBytes = Color.alpha(color);
592         color &= 0x00FFFFFF;
593         color |= ((int) (alphaBytes * alpha)) << 24;
594         return color;
595     }
596 
597     @Override
inflate(Resources res, XmlPullParser parser, AttributeSet attrs)598     public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs)
599             throws XmlPullParserException, IOException {
600         if (mDelegateDrawable != null) {
601             mDelegateDrawable.inflate(res, parser, attrs);
602             return;
603         }
604 
605         inflate(res, parser, attrs, null);
606     }
607 
608     @Override
inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)609     public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
610             throws XmlPullParserException, IOException {
611         if (mDelegateDrawable != null) {
612             DrawableCompat.inflate(mDelegateDrawable, res, parser, attrs, theme);
613             return;
614         }
615 
616         final VectorDrawableCompatState state = mVectorState;
617         final VPathRenderer pathRenderer = new VPathRenderer();
618         state.mVPathRenderer = pathRenderer;
619 
620         final TypedArray a = TypedArrayUtils.obtainAttributes(res, theme, attrs,
621                 AndroidResources.STYLEABLE_VECTOR_DRAWABLE_TYPE_ARRAY);
622 
623         updateStateFromTypedArray(a, parser);
624         a.recycle();
625         state.mChangingConfigurations = getChangingConfigurations();
626         state.mCacheDirty = true;
627         inflateInternal(res, parser, attrs, theme);
628 
629         mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
630     }
631 
632 
633     /**
634      * Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode
635      * attribute's enum value.
636      */
parseTintModeCompat(int value, Mode defaultMode)637     private static PorterDuff.Mode parseTintModeCompat(int value, Mode defaultMode) {
638         switch (value) {
639             case 3:
640                 return Mode.SRC_OVER;
641             case 5:
642                 return Mode.SRC_IN;
643             case 9:
644                 return Mode.SRC_ATOP;
645             case 14:
646                 return Mode.MULTIPLY;
647             case 15:
648                 return Mode.SCREEN;
649             case 16:
650                 if (Build.VERSION.SDK_INT >= 11) {
651                     return Mode.ADD;
652                 } else {
653                     return defaultMode;
654                 }
655             default:
656                 return defaultMode;
657         }
658     }
659 
updateStateFromTypedArray(TypedArray a, XmlPullParser parser)660     private void updateStateFromTypedArray(TypedArray a, XmlPullParser parser)
661             throws XmlPullParserException {
662         final VectorDrawableCompatState state = mVectorState;
663         final VPathRenderer pathRenderer = state.mVPathRenderer;
664 
665         // Account for any configuration changes.
666         // state.mChangingConfigurations |= Utils.getChangingConfigurations(a);
667 
668         final int mode = TypedArrayUtils.getNamedInt(a, parser, "tintMode",
669                 AndroidResources.STYLEABLE_VECTOR_DRAWABLE_TINT_MODE, -1);
670         state.mTintMode = parseTintModeCompat(mode, Mode.SRC_IN);
671 
672         final ColorStateList tint =
673                 a.getColorStateList(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_TINT);
674         if (tint != null) {
675             state.mTint = tint;
676         }
677 
678         state.mAutoMirrored = TypedArrayUtils.getNamedBoolean(a, parser, "autoMirrored",
679                 AndroidResources.STYLEABLE_VECTOR_DRAWABLE_AUTO_MIRRORED, state.mAutoMirrored);
680 
681         pathRenderer.mViewportWidth = TypedArrayUtils.getNamedFloat(a, parser, "viewportWidth",
682                 AndroidResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_WIDTH,
683                 pathRenderer.mViewportWidth);
684 
685         pathRenderer.mViewportHeight = TypedArrayUtils.getNamedFloat(a, parser, "viewportHeight",
686                 AndroidResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_HEIGHT,
687                 pathRenderer.mViewportHeight);
688 
689         if (pathRenderer.mViewportWidth <= 0) {
690             throw new XmlPullParserException(a.getPositionDescription() +
691                     "<vector> tag requires viewportWidth > 0");
692         } else if (pathRenderer.mViewportHeight <= 0) {
693             throw new XmlPullParserException(a.getPositionDescription() +
694                     "<vector> tag requires viewportHeight > 0");
695         }
696 
697         pathRenderer.mBaseWidth = a.getDimension(
698                 AndroidResources.STYLEABLE_VECTOR_DRAWABLE_WIDTH, pathRenderer.mBaseWidth);
699         pathRenderer.mBaseHeight = a.getDimension(
700                 AndroidResources.STYLEABLE_VECTOR_DRAWABLE_HEIGHT, pathRenderer.mBaseHeight);
701         if (pathRenderer.mBaseWidth <= 0) {
702             throw new XmlPullParserException(a.getPositionDescription() +
703                     "<vector> tag requires width > 0");
704         } else if (pathRenderer.mBaseHeight <= 0) {
705             throw new XmlPullParserException(a.getPositionDescription() +
706                     "<vector> tag requires height > 0");
707         }
708 
709         // shown up from API 11.
710         final float alphaInFloat = TypedArrayUtils.getNamedFloat(a, parser, "alpha",
711                 AndroidResources.STYLEABLE_VECTOR_DRAWABLE_ALPHA, pathRenderer.getAlpha());
712         pathRenderer.setAlpha(alphaInFloat);
713 
714         final String name = a.getString(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_NAME);
715         if (name != null) {
716             pathRenderer.mRootName = name;
717             pathRenderer.mVGTargetsMap.put(name, pathRenderer);
718         }
719     }
720 
inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)721     private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
722                                  Theme theme) throws XmlPullParserException, IOException {
723         final VectorDrawableCompatState state = mVectorState;
724         final VPathRenderer pathRenderer = state.mVPathRenderer;
725         boolean noPathTag = true;
726 
727         // Use a stack to help to build the group tree.
728         // The top of the stack is always the current group.
729         final Stack<VGroup> groupStack = new Stack<VGroup>();
730         groupStack.push(pathRenderer.mRootGroup);
731 
732         int eventType = parser.getEventType();
733         final int innerDepth = parser.getDepth() + 1;
734 
735         // Parse everything until the end of the vector element.
736         while (eventType != XmlPullParser.END_DOCUMENT
737                 && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
738             if (eventType == XmlPullParser.START_TAG) {
739                 final String tagName = parser.getName();
740                 final VGroup currentGroup = groupStack.peek();
741                 if (SHAPE_PATH.equals(tagName)) {
742                     final VFullPath path = new VFullPath();
743                     path.inflate(res, attrs, theme, parser);
744                     currentGroup.mChildren.add(path);
745                     if (path.getPathName() != null) {
746                         pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
747                     }
748                     noPathTag = false;
749                     state.mChangingConfigurations |= path.mChangingConfigurations;
750                 } else if (SHAPE_CLIP_PATH.equals(tagName)) {
751                     final VClipPath path = new VClipPath();
752                     path.inflate(res, attrs, theme, parser);
753                     currentGroup.mChildren.add(path);
754                     if (path.getPathName() != null) {
755                         pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
756                     }
757                     state.mChangingConfigurations |= path.mChangingConfigurations;
758                 } else if (SHAPE_GROUP.equals(tagName)) {
759                     VGroup newChildGroup = new VGroup();
760                     newChildGroup.inflate(res, attrs, theme, parser);
761                     currentGroup.mChildren.add(newChildGroup);
762                     groupStack.push(newChildGroup);
763                     if (newChildGroup.getGroupName() != null) {
764                         pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(),
765                                 newChildGroup);
766                     }
767                     state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
768                 }
769             } else if (eventType == XmlPullParser.END_TAG) {
770                 final String tagName = parser.getName();
771                 if (SHAPE_GROUP.equals(tagName)) {
772                     groupStack.pop();
773                 }
774             }
775             eventType = parser.next();
776         }
777 
778         // Print the tree out for debug.
779         if (DBG_VECTOR_DRAWABLE) {
780             printGroupTree(pathRenderer.mRootGroup, 0);
781         }
782 
783         if (noPathTag) {
784             final StringBuffer tag = new StringBuffer();
785 
786             if (tag.length() > 0) {
787                 tag.append(" or ");
788             }
789             tag.append(SHAPE_PATH);
790 
791             throw new XmlPullParserException("no " + tag + " defined");
792         }
793     }
794 
printGroupTree(VGroup currentGroup, int level)795     private void printGroupTree(VGroup currentGroup, int level) {
796         String indent = "";
797         for (int i = 0; i < level; i++) {
798             indent += "    ";
799         }
800         // Print the current node
801         Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName()
802                 + " rotation is " + currentGroup.mRotate);
803         Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString());
804         // Then print all the children groups
805         for (int i = 0; i < currentGroup.mChildren.size(); i++) {
806             Object child = currentGroup.mChildren.get(i);
807             if (child instanceof VGroup) {
808                 printGroupTree((VGroup) child, level + 1);
809             } else {
810                 ((VPath) child).printVPath(level + 1);
811             }
812         }
813     }
814 
setAllowCaching(boolean allowCaching)815     void setAllowCaching(boolean allowCaching) {
816         mAllowCaching = allowCaching;
817     }
818 
819     // We don't support RTL auto mirroring since the getLayoutDirection() is for API 17+.
needMirroring()820     private boolean needMirroring() {
821         if (Build.VERSION.SDK_INT >= 17) {
822             return isAutoMirrored()
823                     && DrawableCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
824         } else {
825             return false;
826         }
827     }
828 
829     // Extra override functions for delegation for SDK >= 7.
830     @Override
onBoundsChange(Rect bounds)831     protected void onBoundsChange(Rect bounds) {
832         if (mDelegateDrawable != null) {
833             mDelegateDrawable.setBounds(bounds);
834         }
835     }
836 
837     @Override
getChangingConfigurations()838     public int getChangingConfigurations() {
839         if (mDelegateDrawable != null) {
840             return mDelegateDrawable.getChangingConfigurations();
841         }
842         return super.getChangingConfigurations() | mVectorState.getChangingConfigurations();
843     }
844 
845     @Override
invalidateSelf()846     public void invalidateSelf() {
847         if (mDelegateDrawable != null) {
848             mDelegateDrawable.invalidateSelf();
849             return;
850         }
851         super.invalidateSelf();
852     }
853 
854     @Override
scheduleSelf(Runnable what, long when)855     public void scheduleSelf(Runnable what, long when) {
856         if (mDelegateDrawable != null) {
857             mDelegateDrawable.scheduleSelf(what, when);
858             return;
859         }
860         super.scheduleSelf(what, when);
861     }
862 
863     @Override
setVisible(boolean visible, boolean restart)864     public boolean setVisible(boolean visible, boolean restart) {
865         if (mDelegateDrawable != null) {
866             return mDelegateDrawable.setVisible(visible, restart);
867         }
868         return super.setVisible(visible, restart);
869     }
870 
871     @Override
unscheduleSelf(Runnable what)872     public void unscheduleSelf(Runnable what) {
873         if (mDelegateDrawable != null) {
874             mDelegateDrawable.unscheduleSelf(what);
875             return;
876         }
877         super.unscheduleSelf(what);
878     }
879 
880     /**
881      * Constant state for delegating the creating drawable job for SDK >= 24.
882      * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains
883      * a delegated VectorDrawable instance.
884      */
885     @RequiresApi(24)
886     private static class VectorDrawableDelegateState extends ConstantState {
887         private final ConstantState mDelegateState;
888 
VectorDrawableDelegateState(ConstantState state)889         public VectorDrawableDelegateState(ConstantState state) {
890             mDelegateState = state;
891         }
892 
893         @Override
newDrawable()894         public Drawable newDrawable() {
895             VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
896             drawableCompat.mDelegateDrawable = (VectorDrawable) mDelegateState.newDrawable();
897             return drawableCompat;
898         }
899 
900         @Override
newDrawable(Resources res)901         public Drawable newDrawable(Resources res) {
902             VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
903             drawableCompat.mDelegateDrawable = (VectorDrawable) mDelegateState.newDrawable(res);
904             return drawableCompat;
905         }
906 
907         @Override
newDrawable(Resources res, Theme theme)908         public Drawable newDrawable(Resources res, Theme theme) {
909             VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
910             drawableCompat.mDelegateDrawable =
911                     (VectorDrawable) mDelegateState.newDrawable(res, theme);
912             return drawableCompat;
913         }
914 
915         @Override
canApplyTheme()916         public boolean canApplyTheme() {
917             return mDelegateState.canApplyTheme();
918         }
919 
920         @Override
getChangingConfigurations()921         public int getChangingConfigurations() {
922             return mDelegateState.getChangingConfigurations();
923         }
924     }
925 
926     private static class VectorDrawableCompatState extends ConstantState {
927         int mChangingConfigurations;
928         VPathRenderer mVPathRenderer;
929         ColorStateList mTint = null;
930         Mode mTintMode = DEFAULT_TINT_MODE;
931         boolean mAutoMirrored;
932 
933         Bitmap mCachedBitmap;
934         int[] mCachedThemeAttrs;
935         ColorStateList mCachedTint;
936         Mode mCachedTintMode;
937         int mCachedRootAlpha;
938         boolean mCachedAutoMirrored;
939         boolean mCacheDirty;
940 
941         /**
942          * Temporary paint object used to draw cached bitmaps.
943          */
944         Paint mTempPaint;
945 
946         // Deep copy for mutate() or implicitly mutate.
VectorDrawableCompatState(VectorDrawableCompatState copy)947         public VectorDrawableCompatState(VectorDrawableCompatState copy) {
948             if (copy != null) {
949                 mChangingConfigurations = copy.mChangingConfigurations;
950                 mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
951                 if (copy.mVPathRenderer.mFillPaint != null) {
952                     mVPathRenderer.mFillPaint = new Paint(copy.mVPathRenderer.mFillPaint);
953                 }
954                 if (copy.mVPathRenderer.mStrokePaint != null) {
955                     mVPathRenderer.mStrokePaint = new Paint(copy.mVPathRenderer.mStrokePaint);
956                 }
957                 mTint = copy.mTint;
958                 mTintMode = copy.mTintMode;
959                 mAutoMirrored = copy.mAutoMirrored;
960             }
961         }
962 
drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter, Rect originalBounds)963         public void drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter,
964                                                   Rect originalBounds) {
965             // The bitmap's size is the same as the bounds.
966             final Paint p = getPaint(filter);
967             canvas.drawBitmap(mCachedBitmap, null, originalBounds, p);
968         }
969 
hasTranslucentRoot()970         public boolean hasTranslucentRoot() {
971             return mVPathRenderer.getRootAlpha() < 255;
972         }
973 
974         /**
975          * @return null when there is no need for alpha paint.
976          */
getPaint(ColorFilter filter)977         public Paint getPaint(ColorFilter filter) {
978             if (!hasTranslucentRoot() && filter == null) {
979                 return null;
980             }
981 
982             if (mTempPaint == null) {
983                 mTempPaint = new Paint();
984                 mTempPaint.setFilterBitmap(true);
985             }
986             mTempPaint.setAlpha(mVPathRenderer.getRootAlpha());
987             mTempPaint.setColorFilter(filter);
988             return mTempPaint;
989         }
990 
updateCachedBitmap(int width, int height)991         public void updateCachedBitmap(int width, int height) {
992             mCachedBitmap.eraseColor(Color.TRANSPARENT);
993             Canvas tmpCanvas = new Canvas(mCachedBitmap);
994             mVPathRenderer.draw(tmpCanvas, width, height, null);
995         }
996 
createCachedBitmapIfNeeded(int width, int height)997         public void createCachedBitmapIfNeeded(int width, int height) {
998             if (mCachedBitmap == null || !canReuseBitmap(width, height)) {
999                 mCachedBitmap = Bitmap.createBitmap(width, height,
1000                         Bitmap.Config.ARGB_8888);
1001                 mCacheDirty = true;
1002             }
1003 
1004         }
1005 
canReuseBitmap(int width, int height)1006         public boolean canReuseBitmap(int width, int height) {
1007             if (width == mCachedBitmap.getWidth()
1008                     && height == mCachedBitmap.getHeight()) {
1009                 return true;
1010             }
1011             return false;
1012         }
1013 
canReuseCache()1014         public boolean canReuseCache() {
1015             if (!mCacheDirty
1016                     && mCachedTint == mTint
1017                     && mCachedTintMode == mTintMode
1018                     && mCachedAutoMirrored == mAutoMirrored
1019                     && mCachedRootAlpha == mVPathRenderer.getRootAlpha()) {
1020                 return true;
1021             }
1022             return false;
1023         }
1024 
updateCacheStates()1025         public void updateCacheStates() {
1026             // Use shallow copy here and shallow comparison in canReuseCache(),
1027             // likely hit cache miss more, but practically not much difference.
1028             mCachedTint = mTint;
1029             mCachedTintMode = mTintMode;
1030             mCachedRootAlpha = mVPathRenderer.getRootAlpha();
1031             mCachedAutoMirrored = mAutoMirrored;
1032             mCacheDirty = false;
1033         }
1034 
VectorDrawableCompatState()1035         public VectorDrawableCompatState() {
1036             mVPathRenderer = new VPathRenderer();
1037         }
1038 
1039         @Override
newDrawable()1040         public Drawable newDrawable() {
1041             return new VectorDrawableCompat(this);
1042         }
1043 
1044         @Override
newDrawable(Resources res)1045         public Drawable newDrawable(Resources res) {
1046             return new VectorDrawableCompat(this);
1047         }
1048 
1049         @Override
getChangingConfigurations()1050         public int getChangingConfigurations() {
1051             return mChangingConfigurations;
1052         }
1053     }
1054 
1055     private static class VPathRenderer {
1056         /* Right now the internal data structure is organized as a tree.
1057          * Each node can be a group node, or a path.
1058          * A group node can have groups or paths as children, but a path node has
1059          * no children.
1060          * One example can be:
1061          *                 Root Group
1062          *                /    |     \
1063          *           Group    Path    Group
1064          *          /     \             |
1065          *         Path   Path         Path
1066          *
1067          */
1068         // Variables that only used temporarily inside the draw() call, so there
1069         // is no need for deep copying.
1070         private final Path mPath;
1071         private final Path mRenderPath;
1072         private static final Matrix IDENTITY_MATRIX = new Matrix();
1073         private final Matrix mFinalPathMatrix = new Matrix();
1074 
1075         private Paint mStrokePaint;
1076         private Paint mFillPaint;
1077         private PathMeasure mPathMeasure;
1078 
1079         /////////////////////////////////////////////////////
1080         // Variables below need to be copied (deep copy if applicable) for mutation.
1081         private int mChangingConfigurations;
1082         final VGroup mRootGroup;
1083         float mBaseWidth = 0;
1084         float mBaseHeight = 0;
1085         float mViewportWidth = 0;
1086         float mViewportHeight = 0;
1087         int mRootAlpha = 0xFF;
1088         String mRootName = null;
1089 
1090         final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>();
1091 
VPathRenderer()1092         public VPathRenderer() {
1093             mRootGroup = new VGroup();
1094             mPath = new Path();
1095             mRenderPath = new Path();
1096         }
1097 
setRootAlpha(int alpha)1098         public void setRootAlpha(int alpha) {
1099             mRootAlpha = alpha;
1100         }
1101 
getRootAlpha()1102         public int getRootAlpha() {
1103             return mRootAlpha;
1104         }
1105 
1106         // setAlpha() and getAlpha() are used mostly for animation purpose, since
1107         // Animator like to use alpha from 0 to 1.
setAlpha(float alpha)1108         public void setAlpha(float alpha) {
1109             setRootAlpha((int) (alpha * 255));
1110         }
1111 
1112         @SuppressWarnings("unused")
getAlpha()1113         public float getAlpha() {
1114             return getRootAlpha() / 255.0f;
1115         }
1116 
VPathRenderer(VPathRenderer copy)1117         public VPathRenderer(VPathRenderer copy) {
1118             mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
1119             mPath = new Path(copy.mPath);
1120             mRenderPath = new Path(copy.mRenderPath);
1121             mBaseWidth = copy.mBaseWidth;
1122             mBaseHeight = copy.mBaseHeight;
1123             mViewportWidth = copy.mViewportWidth;
1124             mViewportHeight = copy.mViewportHeight;
1125             mChangingConfigurations = copy.mChangingConfigurations;
1126             mRootAlpha = copy.mRootAlpha;
1127             mRootName = copy.mRootName;
1128             if (copy.mRootName != null) {
1129                 mVGTargetsMap.put(copy.mRootName, this);
1130             }
1131         }
1132 
drawGroupTree(VGroup currentGroup, Matrix currentMatrix, Canvas canvas, int w, int h, ColorFilter filter)1133         private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
1134                                    Canvas canvas, int w, int h, ColorFilter filter) {
1135             // Calculate current group's matrix by preConcat the parent's and
1136             // and the current one on the top of the stack.
1137             // Basically the Mfinal = Mviewport * M0 * M1 * M2;
1138             // Mi the local matrix at level i of the group tree.
1139             currentGroup.mStackedMatrix.set(currentMatrix);
1140 
1141             currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
1142 
1143             // Save the current clip information, which is local to this group.
1144             canvas.save();
1145 
1146             // Draw the group tree in the same order as the XML file.
1147             for (int i = 0; i < currentGroup.mChildren.size(); i++) {
1148                 Object child = currentGroup.mChildren.get(i);
1149                 if (child instanceof VGroup) {
1150                     VGroup childGroup = (VGroup) child;
1151                     drawGroupTree(childGroup, currentGroup.mStackedMatrix,
1152                             canvas, w, h, filter);
1153                 } else if (child instanceof VPath) {
1154                     VPath childPath = (VPath) child;
1155                     drawPath(currentGroup, childPath, canvas, w, h, filter);
1156                 }
1157             }
1158 
1159             canvas.restore();
1160         }
1161 
draw(Canvas canvas, int w, int h, ColorFilter filter)1162         public void draw(Canvas canvas, int w, int h, ColorFilter filter) {
1163             // Traverse the tree in pre-order to draw.
1164             drawGroupTree(mRootGroup, IDENTITY_MATRIX, canvas, w, h, filter);
1165         }
1166 
drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h, ColorFilter filter)1167         private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h,
1168                               ColorFilter filter) {
1169             final float scaleX = w / mViewportWidth;
1170             final float scaleY = h / mViewportHeight;
1171             final float minScale = Math.min(scaleX, scaleY);
1172             final Matrix groupStackedMatrix = vGroup.mStackedMatrix;
1173 
1174             mFinalPathMatrix.set(groupStackedMatrix);
1175             mFinalPathMatrix.postScale(scaleX, scaleY);
1176 
1177 
1178             final float matrixScale = getMatrixScale(groupStackedMatrix);
1179             if (matrixScale == 0) {
1180                 // When either x or y is scaled to 0, we don't need to draw anything.
1181                 return;
1182             }
1183             vPath.toPath(mPath);
1184             final Path path = mPath;
1185 
1186             mRenderPath.reset();
1187 
1188             if (vPath.isClipPath()) {
1189                 mRenderPath.addPath(path, mFinalPathMatrix);
1190                 canvas.clipPath(mRenderPath);
1191             } else {
1192                 VFullPath fullPath = (VFullPath) vPath;
1193                 if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
1194                     float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
1195                     float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;
1196 
1197                     if (mPathMeasure == null) {
1198                         mPathMeasure = new PathMeasure();
1199                     }
1200                     mPathMeasure.setPath(mPath, false);
1201 
1202                     float len = mPathMeasure.getLength();
1203                     start = start * len;
1204                     end = end * len;
1205                     path.reset();
1206                     if (start > end) {
1207                         mPathMeasure.getSegment(start, len, path, true);
1208                         mPathMeasure.getSegment(0f, end, path, true);
1209                     } else {
1210                         mPathMeasure.getSegment(start, end, path, true);
1211                     }
1212                     path.rLineTo(0, 0); // fix bug in measure
1213                 }
1214                 mRenderPath.addPath(path, mFinalPathMatrix);
1215 
1216                 if (fullPath.mFillColor != Color.TRANSPARENT) {
1217                     if (mFillPaint == null) {
1218                         mFillPaint = new Paint();
1219                         mFillPaint.setStyle(Paint.Style.FILL);
1220                         mFillPaint.setAntiAlias(true);
1221                     }
1222 
1223                     final Paint fillPaint = mFillPaint;
1224                     fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha));
1225                     fillPaint.setColorFilter(filter);
1226                     mRenderPath.setFillType(fullPath.mFillRule == 0 ? Path.FillType.WINDING
1227                             : Path.FillType.EVEN_ODD);
1228                     canvas.drawPath(mRenderPath, fillPaint);
1229                 }
1230 
1231                 if (fullPath.mStrokeColor != Color.TRANSPARENT) {
1232                     if (mStrokePaint == null) {
1233                         mStrokePaint = new Paint();
1234                         mStrokePaint.setStyle(Paint.Style.STROKE);
1235                         mStrokePaint.setAntiAlias(true);
1236                     }
1237 
1238                     final Paint strokePaint = mStrokePaint;
1239                     if (fullPath.mStrokeLineJoin != null) {
1240                         strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
1241                     }
1242 
1243                     if (fullPath.mStrokeLineCap != null) {
1244                         strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
1245                     }
1246 
1247                     strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
1248                     strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha));
1249                     strokePaint.setColorFilter(filter);
1250                     final float finalStrokeScale = minScale * matrixScale;
1251                     strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
1252                     canvas.drawPath(mRenderPath, strokePaint);
1253                 }
1254             }
1255         }
1256 
cross(float v1x, float v1y, float v2x, float v2y)1257         private static float cross(float v1x, float v1y, float v2x, float v2y) {
1258             return v1x * v2y - v1y * v2x;
1259         }
1260 
getMatrixScale(Matrix groupStackedMatrix)1261         private float getMatrixScale(Matrix groupStackedMatrix) {
1262             // Given unit vectors A = (0, 1) and B = (1, 0).
1263             // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
1264             // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
1265             // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
1266             // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
1267             //
1268             // For non-skew case, which is most of the cases, matrix scale is computing exactly the
1269             // scale on x and y axis, and take the minimal of these two.
1270             // For skew case, an unit square will mapped to a parallelogram. And this function will
1271             // return the minimal height of the 2 bases.
1272             float[] unitVectors = new float[]{0, 1, 1, 0};
1273             groupStackedMatrix.mapVectors(unitVectors);
1274             float scaleX = (float) Math.hypot(unitVectors[0], unitVectors[1]);
1275             float scaleY = (float) Math.hypot(unitVectors[2], unitVectors[3]);
1276             float crossProduct = cross(unitVectors[0], unitVectors[1], unitVectors[2],
1277                     unitVectors[3]);
1278             float maxScale = Math.max(scaleX, scaleY);
1279 
1280             float matrixScale = 0;
1281             if (maxScale > 0) {
1282                 matrixScale = Math.abs(crossProduct) / maxScale;
1283             }
1284             if (DBG_VECTOR_DRAWABLE) {
1285                 Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
1286             }
1287             return matrixScale;
1288         }
1289     }
1290 
1291     private static class VGroup {
1292         // mStackedMatrix is only used temporarily when drawing, it combines all
1293         // the parents' local matrices with the current one.
1294         private final Matrix mStackedMatrix = new Matrix();
1295 
1296         /////////////////////////////////////////////////////
1297         // Variables below need to be copied (deep copy if applicable) for mutation.
1298         final ArrayList<Object> mChildren = new ArrayList<Object>();
1299 
1300         float mRotate = 0;
1301         private float mPivotX = 0;
1302         private float mPivotY = 0;
1303         private float mScaleX = 1;
1304         private float mScaleY = 1;
1305         private float mTranslateX = 0;
1306         private float mTranslateY = 0;
1307 
1308         // mLocalMatrix is updated based on the update of transformation information,
1309         // either parsed from the XML or by animation.
1310         private final Matrix mLocalMatrix = new Matrix();
1311         int mChangingConfigurations;
1312         private int[] mThemeAttrs;
1313         private String mGroupName = null;
1314 
VGroup(VGroup copy, ArrayMap<String, Object> targetsMap)1315         public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
1316             mRotate = copy.mRotate;
1317             mPivotX = copy.mPivotX;
1318             mPivotY = copy.mPivotY;
1319             mScaleX = copy.mScaleX;
1320             mScaleY = copy.mScaleY;
1321             mTranslateX = copy.mTranslateX;
1322             mTranslateY = copy.mTranslateY;
1323             mThemeAttrs = copy.mThemeAttrs;
1324             mGroupName = copy.mGroupName;
1325             mChangingConfigurations = copy.mChangingConfigurations;
1326             if (mGroupName != null) {
1327                 targetsMap.put(mGroupName, this);
1328             }
1329 
1330             mLocalMatrix.set(copy.mLocalMatrix);
1331 
1332             final ArrayList<Object> children = copy.mChildren;
1333             for (int i = 0; i < children.size(); i++) {
1334                 Object copyChild = children.get(i);
1335                 if (copyChild instanceof VGroup) {
1336                     VGroup copyGroup = (VGroup) copyChild;
1337                     mChildren.add(new VGroup(copyGroup, targetsMap));
1338                 } else {
1339                     VPath newPath = null;
1340                     if (copyChild instanceof VFullPath) {
1341                         newPath = new VFullPath((VFullPath) copyChild);
1342                     } else if (copyChild instanceof VClipPath) {
1343                         newPath = new VClipPath((VClipPath) copyChild);
1344                     } else {
1345                         throw new IllegalStateException("Unknown object in the tree!");
1346                     }
1347                     mChildren.add(newPath);
1348                     if (newPath.mPathName != null) {
1349                         targetsMap.put(newPath.mPathName, newPath);
1350                     }
1351                 }
1352             }
1353         }
1354 
VGroup()1355         public VGroup() {
1356         }
1357 
getGroupName()1358         public String getGroupName() {
1359             return mGroupName;
1360         }
1361 
getLocalMatrix()1362         public Matrix getLocalMatrix() {
1363             return mLocalMatrix;
1364         }
1365 
inflate(Resources res, AttributeSet attrs, Theme theme, XmlPullParser parser)1366         public void inflate(Resources res, AttributeSet attrs, Theme theme, XmlPullParser parser) {
1367             final TypedArray a = TypedArrayUtils.obtainAttributes(res, theme, attrs,
1368                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP);
1369             updateStateFromTypedArray(a, parser);
1370             a.recycle();
1371         }
1372 
updateStateFromTypedArray(TypedArray a, XmlPullParser parser)1373         private void updateStateFromTypedArray(TypedArray a, XmlPullParser parser) {
1374             // Account for any configuration changes.
1375             // mChangingConfigurations |= Utils.getChangingConfigurations(a);
1376 
1377             // Extract the theme attributes, if any.
1378             mThemeAttrs = null; // TODO TINT THEME Not supported yet a.extractThemeAttrs();
1379 
1380             // This is added in API 11
1381             mRotate = TypedArrayUtils.getNamedFloat(a, parser, "rotation",
1382                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_ROTATION, mRotate);
1383 
1384             mPivotX = a.getFloat(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_X, mPivotX);
1385             mPivotY = a.getFloat(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_Y, mPivotY);
1386 
1387             // This is added in API 11
1388             mScaleX = TypedArrayUtils.getNamedFloat(a, parser, "scaleX",
1389                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_X, mScaleX);
1390 
1391             // This is added in API 11
1392             mScaleY = TypedArrayUtils.getNamedFloat(a, parser, "scaleY",
1393                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_Y, mScaleY);
1394 
1395             mTranslateX = TypedArrayUtils.getNamedFloat(a, parser, "translateX",
1396                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_X, mTranslateX);
1397             mTranslateY = TypedArrayUtils.getNamedFloat(a, parser, "translateY",
1398                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_Y, mTranslateY);
1399 
1400             final String groupName =
1401                     a.getString(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_NAME);
1402             if (groupName != null) {
1403                 mGroupName = groupName;
1404             }
1405 
1406             updateLocalMatrix();
1407         }
1408 
updateLocalMatrix()1409         private void updateLocalMatrix() {
1410             // The order we apply is the same as the
1411             // RenderNode.cpp::applyViewPropertyTransforms().
1412             mLocalMatrix.reset();
1413             mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
1414             mLocalMatrix.postScale(mScaleX, mScaleY);
1415             mLocalMatrix.postRotate(mRotate, 0, 0);
1416             mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
1417         }
1418 
1419         /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1420         @SuppressWarnings("unused")
getRotation()1421         public float getRotation() {
1422             return mRotate;
1423         }
1424 
1425         @SuppressWarnings("unused")
setRotation(float rotation)1426         public void setRotation(float rotation) {
1427             if (rotation != mRotate) {
1428                 mRotate = rotation;
1429                 updateLocalMatrix();
1430             }
1431         }
1432 
1433         @SuppressWarnings("unused")
getPivotX()1434         public float getPivotX() {
1435             return mPivotX;
1436         }
1437 
1438         @SuppressWarnings("unused")
setPivotX(float pivotX)1439         public void setPivotX(float pivotX) {
1440             if (pivotX != mPivotX) {
1441                 mPivotX = pivotX;
1442                 updateLocalMatrix();
1443             }
1444         }
1445 
1446         @SuppressWarnings("unused")
getPivotY()1447         public float getPivotY() {
1448             return mPivotY;
1449         }
1450 
1451         @SuppressWarnings("unused")
setPivotY(float pivotY)1452         public void setPivotY(float pivotY) {
1453             if (pivotY != mPivotY) {
1454                 mPivotY = pivotY;
1455                 updateLocalMatrix();
1456             }
1457         }
1458 
1459         @SuppressWarnings("unused")
getScaleX()1460         public float getScaleX() {
1461             return mScaleX;
1462         }
1463 
1464         @SuppressWarnings("unused")
setScaleX(float scaleX)1465         public void setScaleX(float scaleX) {
1466             if (scaleX != mScaleX) {
1467                 mScaleX = scaleX;
1468                 updateLocalMatrix();
1469             }
1470         }
1471 
1472         @SuppressWarnings("unused")
getScaleY()1473         public float getScaleY() {
1474             return mScaleY;
1475         }
1476 
1477         @SuppressWarnings("unused")
setScaleY(float scaleY)1478         public void setScaleY(float scaleY) {
1479             if (scaleY != mScaleY) {
1480                 mScaleY = scaleY;
1481                 updateLocalMatrix();
1482             }
1483         }
1484 
1485         @SuppressWarnings("unused")
getTranslateX()1486         public float getTranslateX() {
1487             return mTranslateX;
1488         }
1489 
1490         @SuppressWarnings("unused")
setTranslateX(float translateX)1491         public void setTranslateX(float translateX) {
1492             if (translateX != mTranslateX) {
1493                 mTranslateX = translateX;
1494                 updateLocalMatrix();
1495             }
1496         }
1497 
1498         @SuppressWarnings("unused")
getTranslateY()1499         public float getTranslateY() {
1500             return mTranslateY;
1501         }
1502 
1503         @SuppressWarnings("unused")
setTranslateY(float translateY)1504         public void setTranslateY(float translateY) {
1505             if (translateY != mTranslateY) {
1506                 mTranslateY = translateY;
1507                 updateLocalMatrix();
1508             }
1509         }
1510     }
1511 
1512     /**
1513      * Common Path information for clip path and normal path.
1514      */
1515     private static class VPath {
1516         protected PathParser.PathDataNode[] mNodes = null;
1517         String mPathName;
1518         int mChangingConfigurations;
1519 
VPath()1520         public VPath() {
1521             // Empty constructor.
1522         }
1523 
printVPath(int level)1524         public void printVPath(int level) {
1525             String indent = "";
1526             for (int i = 0; i < level; i++) {
1527                 indent += "    ";
1528             }
1529             Log.v(LOGTAG, indent + "current path is :" + mPathName +
1530                     " pathData is " + nodesToString(mNodes));
1531 
1532         }
1533 
nodesToString(PathParser.PathDataNode[] nodes)1534         public String nodesToString(PathParser.PathDataNode[] nodes) {
1535             String result = " ";
1536             for (int i = 0; i < nodes.length; i++) {
1537                 result += nodes[i].mType + ":";
1538                 float[] params = nodes[i].mParams;
1539                 for (int j = 0; j < params.length; j++) {
1540                     result += params[j] + ",";
1541                 }
1542             }
1543             return result;
1544         }
1545 
VPath(VPath copy)1546         public VPath(VPath copy) {
1547             mPathName = copy.mPathName;
1548             mChangingConfigurations = copy.mChangingConfigurations;
1549             mNodes = PathParser.deepCopyNodes(copy.mNodes);
1550         }
1551 
toPath(Path path)1552         public void toPath(Path path) {
1553             path.reset();
1554             if (mNodes != null) {
1555                 PathParser.PathDataNode.nodesToPath(mNodes, path);
1556             }
1557         }
1558 
getPathName()1559         public String getPathName() {
1560             return mPathName;
1561         }
1562 
canApplyTheme()1563         public boolean canApplyTheme() {
1564             return false;
1565         }
1566 
applyTheme(Theme t)1567         public void applyTheme(Theme t) {
1568         }
1569 
isClipPath()1570         public boolean isClipPath() {
1571             return false;
1572         }
1573 
1574         /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1575         @SuppressWarnings("unused")
getPathData()1576         public PathParser.PathDataNode[] getPathData() {
1577             return mNodes;
1578         }
1579 
1580         @SuppressWarnings("unused")
setPathData(PathParser.PathDataNode[] nodes)1581         public void setPathData(PathParser.PathDataNode[] nodes) {
1582             if (!PathParser.canMorph(mNodes, nodes)) {
1583                 // This should not happen in the middle of animation.
1584                 mNodes = PathParser.deepCopyNodes(nodes);
1585             } else {
1586                 PathParser.updateNodes(mNodes, nodes);
1587             }
1588         }
1589     }
1590 
1591     /**
1592      * Clip path, which only has name and pathData.
1593      */
1594     private static class VClipPath extends VPath {
VClipPath()1595         public VClipPath() {
1596             // Empty constructor.
1597         }
1598 
VClipPath(VClipPath copy)1599         public VClipPath(VClipPath copy) {
1600             super(copy);
1601         }
1602 
inflate(Resources r, AttributeSet attrs, Theme theme, XmlPullParser parser)1603         public void inflate(Resources r, AttributeSet attrs, Theme theme, XmlPullParser parser) {
1604             // TODO TINT THEME Not supported yet
1605             final boolean hasPathData = TypedArrayUtils.hasAttribute(parser, "pathData");
1606             if (!hasPathData) {
1607                 return;
1608             }
1609             final TypedArray a = TypedArrayUtils.obtainAttributes(r, theme, attrs,
1610                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH);
1611             updateStateFromTypedArray(a);
1612             a.recycle();
1613         }
1614 
updateStateFromTypedArray(TypedArray a)1615         private void updateStateFromTypedArray(TypedArray a) {
1616             // Account for any configuration changes.
1617             // mChangingConfigurations |= Utils.getChangingConfigurations(a);;
1618 
1619             final String pathName =
1620                     a.getString(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_NAME);
1621             if (pathName != null) {
1622                 mPathName = pathName;
1623             }
1624 
1625             final String pathData =
1626                     a.getString(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_PATH_DATA);
1627             if (pathData != null) {
1628                 mNodes = PathParser.createNodesFromPathData(pathData);
1629             }
1630         }
1631 
1632         @Override
isClipPath()1633         public boolean isClipPath() {
1634             return true;
1635         }
1636     }
1637 
1638     /**
1639      * Normal path, which contains all the fill / paint information.
1640      */
1641     private static class VFullPath extends VPath {
1642         /////////////////////////////////////////////////////
1643         // Variables below need to be copied (deep copy if applicable) for mutation.
1644         private int[] mThemeAttrs;
1645         private static final int FILL_TYPE_WINDING = 0;
1646         int mStrokeColor = Color.TRANSPARENT;
1647         float mStrokeWidth = 0;
1648 
1649         int mFillColor = Color.TRANSPARENT;
1650         float mStrokeAlpha = 1.0f;
1651         // Default fill rule is winding, or as known as "non-zero".
1652         int mFillRule = FILL_TYPE_WINDING;
1653         float mFillAlpha = 1.0f;
1654         float mTrimPathStart = 0;
1655         float mTrimPathEnd = 1;
1656         float mTrimPathOffset = 0;
1657 
1658         Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
1659         Paint.Join mStrokeLineJoin = Paint.Join.MITER;
1660         float mStrokeMiterlimit = 4;
1661 
VFullPath()1662         public VFullPath() {
1663             // Empty constructor.
1664         }
1665 
VFullPath(VFullPath copy)1666         public VFullPath(VFullPath copy) {
1667             super(copy);
1668             mThemeAttrs = copy.mThemeAttrs;
1669 
1670             mStrokeColor = copy.mStrokeColor;
1671             mStrokeWidth = copy.mStrokeWidth;
1672             mStrokeAlpha = copy.mStrokeAlpha;
1673             mFillColor = copy.mFillColor;
1674             mFillRule = copy.mFillRule;
1675             mFillAlpha = copy.mFillAlpha;
1676             mTrimPathStart = copy.mTrimPathStart;
1677             mTrimPathEnd = copy.mTrimPathEnd;
1678             mTrimPathOffset = copy.mTrimPathOffset;
1679 
1680             mStrokeLineCap = copy.mStrokeLineCap;
1681             mStrokeLineJoin = copy.mStrokeLineJoin;
1682             mStrokeMiterlimit = copy.mStrokeMiterlimit;
1683         }
1684 
getStrokeLineCap(int id, Paint.Cap defValue)1685         private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
1686             switch (id) {
1687                 case LINECAP_BUTT:
1688                     return Paint.Cap.BUTT;
1689                 case LINECAP_ROUND:
1690                     return Paint.Cap.ROUND;
1691                 case LINECAP_SQUARE:
1692                     return Paint.Cap.SQUARE;
1693                 default:
1694                     return defValue;
1695             }
1696         }
1697 
getStrokeLineJoin(int id, Paint.Join defValue)1698         private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) {
1699             switch (id) {
1700                 case LINEJOIN_MITER:
1701                     return Paint.Join.MITER;
1702                 case LINEJOIN_ROUND:
1703                     return Paint.Join.ROUND;
1704                 case LINEJOIN_BEVEL:
1705                     return Paint.Join.BEVEL;
1706                 default:
1707                     return defValue;
1708             }
1709         }
1710 
1711         @Override
canApplyTheme()1712         public boolean canApplyTheme() {
1713             return mThemeAttrs != null;
1714         }
1715 
inflate(Resources r, AttributeSet attrs, Theme theme, XmlPullParser parser)1716         public void inflate(Resources r, AttributeSet attrs, Theme theme, XmlPullParser parser) {
1717             final TypedArray a = TypedArrayUtils.obtainAttributes(r, theme, attrs,
1718                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH);
1719             updateStateFromTypedArray(a, parser);
1720             a.recycle();
1721         }
1722 
updateStateFromTypedArray(TypedArray a, XmlPullParser parser)1723         private void updateStateFromTypedArray(TypedArray a, XmlPullParser parser) {
1724             // Account for any configuration changes.
1725             // mChangingConfigurations |= Utils.getChangingConfigurations(a);
1726 
1727             // Extract the theme attributes, if any.
1728             mThemeAttrs = null; // TODO TINT THEME Not supported yet a.extractThemeAttrs();
1729 
1730             // In order to work around the conflicting id issue, we need to double check the
1731             // existence of the attribute.
1732             // B/c if the attribute existed in the compiled XML, then calling TypedArray will be
1733             // safe since the framework will look up in the XML first.
1734             // Note that each getAttributeValue take roughly 0.03ms, it is a price we have to pay.
1735             final boolean hasPathData = TypedArrayUtils.hasAttribute(parser, "pathData");
1736             if (!hasPathData) {
1737                 // If there is no pathData in the <path> tag, then this is an empty path,
1738                 // nothing need to be drawn.
1739                 return;
1740             }
1741 
1742             final String pathName = a.getString(
1743                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_NAME);
1744             if (pathName != null) {
1745                 mPathName = pathName;
1746             }
1747             final String pathData =
1748                     a.getString(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_PATH_DATA);
1749             if (pathData != null) {
1750                 mNodes = PathParser.createNodesFromPathData(pathData);
1751             }
1752 
1753             mFillColor = TypedArrayUtils.getNamedColor(a, parser, "fillColor",
1754                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_COLOR, mFillColor);
1755             mFillAlpha = TypedArrayUtils.getNamedFloat(a, parser, "fillAlpha",
1756                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_ALPHA, mFillAlpha);
1757             final int lineCap = TypedArrayUtils.getNamedInt(a, parser, "strokeLineCap",
1758                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_CAP, -1);
1759             mStrokeLineCap = getStrokeLineCap(lineCap, mStrokeLineCap);
1760             final int lineJoin = TypedArrayUtils.getNamedInt(a, parser, "strokeLineJoin",
1761                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_JOIN, -1);
1762             mStrokeLineJoin = getStrokeLineJoin(lineJoin, mStrokeLineJoin);
1763             mStrokeMiterlimit = TypedArrayUtils.getNamedFloat(a, parser, "strokeMiterLimit",
1764                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_MITER_LIMIT,
1765                     mStrokeMiterlimit);
1766             mStrokeColor = TypedArrayUtils.getNamedColor(a, parser, "strokeColor",
1767                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_COLOR, mStrokeColor);
1768             mStrokeAlpha = TypedArrayUtils.getNamedFloat(a, parser, "strokeAlpha",
1769                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_ALPHA, mStrokeAlpha);
1770             mStrokeWidth = TypedArrayUtils.getNamedFloat(a, parser, "strokeWidth",
1771                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_WIDTH, mStrokeWidth);
1772             mTrimPathEnd = TypedArrayUtils.getNamedFloat(a, parser, "trimPathEnd",
1773                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_END, mTrimPathEnd);
1774             mTrimPathOffset = TypedArrayUtils.getNamedFloat(a, parser, "trimPathOffset",
1775                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_OFFSET,
1776                     mTrimPathOffset);
1777             mTrimPathStart = TypedArrayUtils.getNamedFloat(a, parser, "trimPathStart",
1778                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_START,
1779                     mTrimPathStart);
1780             mFillRule = TypedArrayUtils.getNamedInt(a, parser, "fillType",
1781                     AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE,
1782                     mFillRule);
1783         }
1784 
1785         @Override
applyTheme(Theme t)1786         public void applyTheme(Theme t) {
1787             if (mThemeAttrs == null) {
1788                 return;
1789             }
1790 
1791             /*
1792              * TODO TINT THEME Not supported yet final TypedArray a =
1793              * t.resolveAttributes(mThemeAttrs, styleable_VectorDrawablePath);
1794              * updateStateFromTypedArray(a); a.recycle();
1795              */
1796         }
1797 
1798         /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1799         @SuppressWarnings("unused")
getStrokeColor()1800         int getStrokeColor() {
1801             return mStrokeColor;
1802         }
1803 
1804         @SuppressWarnings("unused")
setStrokeColor(int strokeColor)1805         void setStrokeColor(int strokeColor) {
1806             mStrokeColor = strokeColor;
1807         }
1808 
1809         @SuppressWarnings("unused")
getStrokeWidth()1810         float getStrokeWidth() {
1811             return mStrokeWidth;
1812         }
1813 
1814         @SuppressWarnings("unused")
setStrokeWidth(float strokeWidth)1815         void setStrokeWidth(float strokeWidth) {
1816             mStrokeWidth = strokeWidth;
1817         }
1818 
1819         @SuppressWarnings("unused")
getStrokeAlpha()1820         float getStrokeAlpha() {
1821             return mStrokeAlpha;
1822         }
1823 
1824         @SuppressWarnings("unused")
setStrokeAlpha(float strokeAlpha)1825         void setStrokeAlpha(float strokeAlpha) {
1826             mStrokeAlpha = strokeAlpha;
1827         }
1828 
1829         @SuppressWarnings("unused")
getFillColor()1830         int getFillColor() {
1831             return mFillColor;
1832         }
1833 
1834         @SuppressWarnings("unused")
setFillColor(int fillColor)1835         void setFillColor(int fillColor) {
1836             mFillColor = fillColor;
1837         }
1838 
1839         @SuppressWarnings("unused")
getFillAlpha()1840         float getFillAlpha() {
1841             return mFillAlpha;
1842         }
1843 
1844         @SuppressWarnings("unused")
setFillAlpha(float fillAlpha)1845         void setFillAlpha(float fillAlpha) {
1846             mFillAlpha = fillAlpha;
1847         }
1848 
1849         @SuppressWarnings("unused")
getTrimPathStart()1850         float getTrimPathStart() {
1851             return mTrimPathStart;
1852         }
1853 
1854         @SuppressWarnings("unused")
setTrimPathStart(float trimPathStart)1855         void setTrimPathStart(float trimPathStart) {
1856             mTrimPathStart = trimPathStart;
1857         }
1858 
1859         @SuppressWarnings("unused")
getTrimPathEnd()1860         float getTrimPathEnd() {
1861             return mTrimPathEnd;
1862         }
1863 
1864         @SuppressWarnings("unused")
setTrimPathEnd(float trimPathEnd)1865         void setTrimPathEnd(float trimPathEnd) {
1866             mTrimPathEnd = trimPathEnd;
1867         }
1868 
1869         @SuppressWarnings("unused")
getTrimPathOffset()1870         float getTrimPathOffset() {
1871             return mTrimPathOffset;
1872         }
1873 
1874         @SuppressWarnings("unused")
setTrimPathOffset(float trimPathOffset)1875         void setTrimPathOffset(float trimPathOffset) {
1876             mTrimPathOffset = trimPathOffset;
1877         }
1878     }
1879 }
1880