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