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