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