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