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