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