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