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