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