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