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