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