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