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