VectorDrawable.java revision 9a77cb92da2b7d3d2c513c9fbc24955fdc0e0693
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.Canvas;
22import android.graphics.Color;
23import android.graphics.ColorFilter;
24import android.graphics.Matrix;
25import android.graphics.Paint;
26import android.graphics.Path;
27import android.graphics.PathMeasure;
28import android.graphics.PixelFormat;
29import android.graphics.PorterDuffColorFilter;
30import android.graphics.Rect;
31import android.graphics.Region;
32import android.graphics.PorterDuff.Mode;
33import android.util.ArrayMap;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.util.PathParser;
37import android.util.Xml;
38
39import com.android.internal.R;
40
41import org.xmlpull.v1.XmlPullParser;
42import org.xmlpull.v1.XmlPullParserException;
43import org.xmlpull.v1.XmlPullParserFactory;
44
45import java.io.IOException;
46import java.util.ArrayList;
47import java.util.Arrays;
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 * <dl>
57 * <dt><code>&lt;vector></code></dt>
58 * <dd>Used to defined a vector drawable</dd>
59 * <dt><code>&lt;size></code></dt>
60 * <dd>Used to defined the intrinsic Width Height size of the drawable using
61 * <code>android:width</code> and <code>android:height</code></dd>
62 * <dt><code>&lt;viewport></code></dt>
63 * <dd>Used to defined the size of the virtual canvas the paths are drawn on.
64 * The size is defined using the attributes <code>android:viewportHeight</code>
65 * <code>android:viewportWidth</code></dd>
66 * <dt><code>&lt;group></code></dt>
67 * <dd>Defines a group of paths or subgroups, plus transformation information.
68 * The transformations are defined in the same coordinates as the viewport.
69 * And the transformations are applied in the order of scale, rotate then translate. </dd>
70 * <dt><code>android:rotation</code>
71 * <dd>The degrees of rotation of the group.</dd></dt>
72 * <dt><code>android:pivotX</code>
73 * <dd>The X coordinate of the pivot for the scale and rotation of the group</dd></dt>
74 * <dt><code>android:pivotY</code>
75 * <dd>The Y coordinate of the pivot for the scale and rotation of the group</dd></dt>
76 * <dt><code>android:scaleX</code>
77 * <dd>The amount of scale on the X Coordinate</dd></dt>
78 * <dt><code>android:scaleY</code>
79 * <dd>The amount of scale on the Y coordinate</dd></dt>
80 * <dt><code>android:translateX</code>
81 * <dd>The amount of translation on the X coordinate</dd></dt>
82 * <dt><code>android:translateY</code>
83 * <dd>The amount of translation on the Y coordinate</dd></dt>
84 * <dt><code>&lt;path></code></dt>
85 * <dd>Defines paths to be drawn.
86 * <dl>
87 * <dt><code>android:name</code>
88 * <dd>Defines the name of the path.</dd></dt>
89 * <dt><code>android:pathData</code>
90 * <dd>Defines path string. This is using exactly same format as "d" attribute
91 * in the SVG's path data</dd></dt>
92 * <dt><code>android:fill</code>
93 * <dd>Defines the color to fill the path (none if not present).</dd></dt>
94 * <dt><code>android:stroke</code>
95 * <dd>Defines the color to draw the path outline (none if not present).</dd>
96 * </dt>
97 * <dt><code>android:strokeWidth</code>
98 * <dd>The width a path stroke</dd></dt>
99 * <dt><code>android:strokeOpacity</code>
100 * <dd>The opacity of a path stroke</dd></dt>
101 * <dt><code>android:fillOpacity</code>
102 * <dd>The opacity to fill the path with</dd></dt>
103 * <dt><code>android:trimPathStart</code>
104 * <dd>The fraction of the path to trim from the start from 0 to 1</dd></dt>
105 * <dt><code>android:trimPathEnd</code>
106 * <dd>The fraction of the path to trim from the end from 0 to 1</dd></dt>
107 * <dt><code>android:trimPathOffset</code>
108 * <dd>Shift trim region (allows showed region to include the start and end)
109 * from 0 to 1</dd></dt>
110 * <dt><code>android:clipToPath</code>
111 * <dd>Path will set the clip path</dd></dt>
112 * <dt><code>android:strokeLineCap</code>
113 * <dd>Sets the linecap for a stroked path: butt, round, square</dd></dt>
114 * <dt><code>android:strokeLineJoin</code>
115 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel</dd></dt>
116 * <dt><code>android:strokeMiterLimit</code>
117 * <dd>Sets the Miter limit for a stroked path</dd></dt>
118 * </dl>
119 * </dd>
120 */
121public class VectorDrawable extends Drawable {
122    private static final String LOGTAG = VectorDrawable.class.getSimpleName();
123
124    private static final String SHAPE_SIZE = "size";
125    private static final String SHAPE_VIEWPORT = "viewport";
126    private static final String SHAPE_GROUP = "group";
127    private static final String SHAPE_PATH = "path";
128    private static final String SHAPE_VECTOR = "vector";
129
130    private static final int LINECAP_BUTT = 0;
131    private static final int LINECAP_ROUND = 1;
132    private static final int LINECAP_SQUARE = 2;
133
134    private static final int LINEJOIN_MITER = 0;
135    private static final int LINEJOIN_ROUND = 1;
136    private static final int LINEJOIN_BEVEL = 2;
137
138    private static final boolean DBG_VECTOR_DRAWABLE = false;
139
140    private final VectorDrawableState mVectorState;
141
142    private final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>();
143
144    private PorterDuffColorFilter mTintFilter;
145
146    public VectorDrawable() {
147        mVectorState = new VectorDrawableState(null);
148    }
149
150    private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) {
151        if (theme != null && state.canApplyTheme()) {
152            // If we need to apply a theme, implicitly mutate.
153            mVectorState = new VectorDrawableState(state);
154            applyTheme(theme);
155        } else {
156            mVectorState = state;
157        }
158
159        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
160        mVectorState.mVPathRenderer.setColorFilter(mTintFilter);
161    }
162
163    Object getTargetByName(String name) {
164        return mVGTargetsMap.get(name);
165    }
166
167    @Override
168    public ConstantState getConstantState() {
169        return mVectorState;
170    }
171
172    @Override
173    public void draw(Canvas canvas) {
174        final int saveCount = canvas.save();
175        final Rect bounds = getBounds();
176        canvas.translate(bounds.left, bounds.top);
177        mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height());
178        canvas.restoreToCount(saveCount);
179    }
180
181    @Override
182    public int getAlpha() {
183        return mVectorState.mVPathRenderer.getRootAlpha();
184    }
185
186    @Override
187    public void setAlpha(int alpha) {
188        if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) {
189            mVectorState.mVPathRenderer.setRootAlpha(alpha);
190            invalidateSelf();
191        }
192    }
193
194    @Override
195    public void setColorFilter(ColorFilter colorFilter) {
196        final VectorDrawableState state = mVectorState;
197        if (colorFilter != null) {
198            // Color filter overrides tint.
199            mTintFilter = null;
200        } else if (state.mTint != null && state.mTintMode != null) {
201            // Restore the tint filter, if we need one.
202            final int color = state.mTint.getColorForState(getState(), Color.TRANSPARENT);
203            mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
204            colorFilter = mTintFilter;
205        }
206
207        state.mVPathRenderer.setColorFilter(colorFilter);
208        invalidateSelf();
209    }
210
211    @Override
212    public void setTint(ColorStateList tint, Mode tintMode) {
213        final VectorDrawableState state = mVectorState;
214        if (state.mTint != tint || state.mTintMode != tintMode) {
215            state.mTint = tint;
216            state.mTintMode = tintMode;
217
218            mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
219            mVectorState.mVPathRenderer.setColorFilter(mTintFilter);
220            invalidateSelf();
221        }
222    }
223
224    @Override
225    protected boolean onStateChange(int[] stateSet) {
226        final VectorDrawableState state = mVectorState;
227        if (state.mTint != null && state.mTintMode != null) {
228            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
229            mVectorState.mVPathRenderer.setColorFilter(mTintFilter);
230            return true;
231        }
232        return false;
233    }
234
235    @Override
236    public int getOpacity() {
237        return PixelFormat.TRANSLUCENT;
238    }
239
240    /**
241     * Sets padding for this shape, defined by a Rect object. Define the padding
242     * in the Rect object as: left, top, right, bottom.
243     */
244    public void setPadding(Rect padding) {
245        setPadding(padding.left, padding.top, padding.right, padding.bottom);
246    }
247
248    /**
249     * Sets padding for the shape.
250     *
251     * @param left padding for the left side (in pixels)
252     * @param top padding for the top (in pixels)
253     * @param right padding for the right side (in pixels)
254     * @param bottom padding for the bottom (in pixels)
255     */
256    public void setPadding(int left, int top, int right, int bottom) {
257        if ((left | top | right | bottom) == 0) {
258            mVectorState.mPadding = null;
259        } else {
260            if (mVectorState.mPadding == null) {
261                mVectorState.mPadding = new Rect();
262            }
263            mVectorState.mPadding.set(left, top, right, bottom);
264        }
265        invalidateSelf();
266    }
267
268    @Override
269    public int getIntrinsicWidth() {
270        return (int) mVectorState.mVPathRenderer.mBaseWidth;
271    }
272
273    @Override
274    public int getIntrinsicHeight() {
275        return (int) mVectorState.mVPathRenderer.mBaseHeight;
276    }
277
278    @Override
279    public boolean getPadding(Rect padding) {
280        if (mVectorState.mPadding != null) {
281            padding.set(mVectorState.mPadding);
282            return true;
283        } else {
284            return super.getPadding(padding);
285        }
286    }
287
288    @Override
289    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
290            throws XmlPullParserException, IOException {
291        final VPathRenderer p = inflateInternal(res, parser, attrs, theme);
292        setPathRenderer(p);
293    }
294
295    @Override
296    public boolean canApplyTheme() {
297        return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme();
298    }
299
300    @Override
301    public void applyTheme(Theme t) {
302        super.applyTheme(t);
303
304        final VectorDrawableState state = mVectorState;
305        final VPathRenderer path = state.mVPathRenderer;
306        if (path != null && path.canApplyTheme()) {
307            path.applyTheme(t);
308        }
309    }
310
311    /** @hide */
312    public static VectorDrawable create(Resources resources, int rid) {
313        try {
314            final XmlPullParser xpp = resources.getXml(rid);
315            final AttributeSet attrs = Xml.asAttributeSet(xpp);
316            final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
317            factory.setNamespaceAware(true);
318
319            final VectorDrawable drawable = new VectorDrawable();
320            drawable.inflate(resources, xpp, attrs);
321
322            return drawable;
323        } catch (XmlPullParserException e) {
324            Log.e(LOGTAG, "parser error", e);
325        } catch (IOException e) {
326            Log.e(LOGTAG, "parser error", e);
327        }
328        return null;
329    }
330
331    private static int applyAlpha(int color, float alpha) {
332        int alphaBytes = Color.alpha(color);
333        color &= 0x00FFFFFF;
334        color |= ((int) (alphaBytes * alpha)) << 24;
335        return color;
336    }
337
338    private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
339            Theme theme) throws XmlPullParserException, IOException {
340        final VPathRenderer pathRenderer = new VPathRenderer();
341
342        boolean noSizeTag = true;
343        boolean noViewportTag = true;
344        boolean noGroupTag = true;
345        boolean noPathTag = true;
346
347        // Use a stack to help to build the group tree.
348        // The top of the stack is always the current group.
349        final Stack<VGroup> groupStack = new Stack<VGroup>();
350        groupStack.push(pathRenderer.mRootGroup);
351
352        int eventType = parser.getEventType();
353        while (eventType != XmlPullParser.END_DOCUMENT) {
354            if (eventType == XmlPullParser.START_TAG) {
355                final String tagName = parser.getName();
356                final VGroup currentGroup = groupStack.peek();
357
358                if (SHAPE_PATH.equals(tagName)) {
359                    final VPath path = new VPath();
360                    path.inflate(res, attrs, theme);
361                    currentGroup.add(path);
362                    if (path.getPathName() != null) {
363                        mVGTargetsMap.put(path.getPathName(), path);
364                    }
365                    noPathTag = false;
366                } else if (SHAPE_SIZE.equals(tagName)) {
367                    pathRenderer.parseSize(res, attrs);
368                    noSizeTag = false;
369                } else if (SHAPE_VIEWPORT.equals(tagName)) {
370                    pathRenderer.parseViewport(res, attrs);
371                    noViewportTag = false;
372                } else if (SHAPE_GROUP.equals(tagName)) {
373                    VGroup newChildGroup = new VGroup();
374                    newChildGroup.inflate(res, attrs, theme);
375                    currentGroup.mChildGroupList.add(newChildGroup);
376                    groupStack.push(newChildGroup);
377                    if (newChildGroup.getGroupName() != null) {
378                        mVGTargetsMap.put(newChildGroup.getGroupName(), newChildGroup);
379                    }
380                    noGroupTag = false;
381                } else if (SHAPE_VECTOR.equals(tagName)) {
382                    parseVector(res, attrs);
383                }
384            } else if (eventType == XmlPullParser.END_TAG) {
385                final String tagName = parser.getName();
386                if (SHAPE_GROUP.equals(tagName)) {
387                    groupStack.pop();
388                }
389            }
390            eventType = parser.next();
391        }
392
393        // Print the tree out for debug.
394        if (DBG_VECTOR_DRAWABLE) {
395            printGroupTree(pathRenderer.mRootGroup, 0);
396        }
397
398        if (noSizeTag || noViewportTag || noPathTag) {
399            final StringBuffer tag = new StringBuffer();
400
401            if (noSizeTag) {
402                tag.append(SHAPE_SIZE);
403            }
404
405            if (noViewportTag) {
406                if (tag.length() > 0) {
407                    tag.append(" & ");
408                }
409                tag.append(SHAPE_SIZE);
410            }
411
412            if (noPathTag) {
413                if (tag.length() > 0) {
414                    tag.append(" or ");
415                }
416                tag.append(SHAPE_PATH);
417            }
418
419            throw new XmlPullParserException("no " + tag + " defined");
420        }
421
422        mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
423        mVectorState.mVPathRenderer.setColorFilter(mTintFilter);
424
425        return pathRenderer;
426    }
427
428    private void parseVector(Resources r, AttributeSet attrs) throws XmlPullParserException {
429        final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawable);
430        final VectorDrawableState state = mVectorState;
431
432        state.mThemeAttrs = a.extractThemeAttrs();
433
434        final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1);
435        if (tintMode != -1) {
436            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
437        }
438
439        final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
440        if (tint != null) {
441            state.mTint = tint;
442        }
443
444        a.recycle();
445    }
446
447    private void printGroupTree(VGroup currentGroup, int level) {
448        String indent = "";
449        for (int i = 0 ; i < level ; i++) {
450            indent += "    ";
451        }
452        // Print the current node
453        Log.v(LOGTAG, indent + "current group is :" +  currentGroup.getGroupName()
454                + " rotation is " + currentGroup.mRotate);
455        Log.v(LOGTAG, indent + "matrix is :" +  currentGroup.getLocalMatrix().toString());
456        // Then print all the children
457        for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
458            printGroupTree(currentGroup.mChildGroupList.get(i), level + 1);
459        }
460    }
461
462    private void setPathRenderer(VPathRenderer pathRenderer) {
463        mVectorState.mVPathRenderer = pathRenderer;
464    }
465
466    private static class VectorDrawableState extends ConstantState {
467        int[] mThemeAttrs;
468        int mChangingConfigurations;
469        VPathRenderer mVPathRenderer;
470        Rect mPadding;
471        ColorStateList mTint;
472        Mode mTintMode;
473
474        public VectorDrawableState(VectorDrawableState copy) {
475            if (copy != null) {
476                mThemeAttrs = copy.mThemeAttrs;
477                mChangingConfigurations = copy.mChangingConfigurations;
478                // TODO: Make sure the constant state are handled correctly.
479                mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
480                mPadding = new Rect(copy.mPadding);
481                mTint = copy.mTint;
482                mTintMode = copy.mTintMode;
483            }
484        }
485
486        @Override
487        public Drawable newDrawable() {
488            return new VectorDrawable(this, null, null);
489        }
490
491        @Override
492        public Drawable newDrawable(Resources res) {
493            return new VectorDrawable(this, res, null);
494        }
495
496        @Override
497        public Drawable newDrawable(Resources res, Theme theme) {
498            return new VectorDrawable(this, res, theme);
499        }
500
501        @Override
502        public int getChangingConfigurations() {
503            return mChangingConfigurations;
504        }
505    }
506
507    private static class VPathRenderer {
508        /* Right now the internal data structure is organized as a tree.
509         * Each node can be a group node, or a path.
510         * A group node can have groups or paths as children, but a path node has
511         * no children.
512         * One example can be:
513         *                 Root Group
514         *                /    |     \
515         *           Group    Path    Group
516         *          /     \             |
517         *         Path   Path         Path
518         *
519         */
520        private final VGroup mRootGroup;
521
522        private final Path mPath = new Path();
523        private final Path mRenderPath = new Path();
524        private static final Matrix IDENTITY_MATRIX = new Matrix();
525
526        private Paint mStrokePaint;
527        private Paint mFillPaint;
528        private ColorFilter mColorFilter;
529        private PathMeasure mPathMeasure;
530
531        private float mBaseWidth = 0;
532        private float mBaseHeight = 0;
533        private float mViewportWidth = 0;
534        private float mViewportHeight = 0;
535        private int mRootAlpha = 0xFF;
536
537        private final Matrix mFinalPathMatrix = new Matrix();
538
539        public VPathRenderer() {
540            mRootGroup = new VGroup();
541        }
542
543        public void setRootAlpha(int alpha) {
544            mRootAlpha = alpha;
545        }
546
547        public int getRootAlpha() {
548            return mRootAlpha;
549        }
550
551        public VPathRenderer(VPathRenderer copy) {
552            mRootGroup = copy.mRootGroup;
553            mBaseWidth = copy.mBaseWidth;
554            mBaseHeight = copy.mBaseHeight;
555            mViewportWidth = copy.mViewportHeight;
556            mViewportHeight = copy.mViewportHeight;
557        }
558
559        public boolean canApplyTheme() {
560            // If one of the paths can apply theme, then return true;
561            return recursiveCanApplyTheme(mRootGroup);
562        }
563
564        private boolean recursiveCanApplyTheme(VGroup currentGroup) {
565            // We can do a tree traverse here, if there is one path return true,
566            // then we return true for the whole tree.
567            final ArrayList<VPath> paths = currentGroup.mPathList;
568            for (int j = paths.size() - 1; j >= 0; j--) {
569                final VPath path = paths.get(j);
570                if (path.canApplyTheme()) {
571                    return true;
572                }
573            }
574
575            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
576
577            for (int i = 0; i < childGroups.size(); i++) {
578                VGroup childGroup = childGroups.get(i);
579                if (childGroup.canApplyTheme()
580                        || recursiveCanApplyTheme(childGroup)) {
581                    return true;
582                }
583            }
584            return false;
585        }
586
587        public void applyTheme(Theme t) {
588            // Apply theme to every path of the tree.
589            recursiveApplyTheme(mRootGroup, t);
590        }
591
592        private void recursiveApplyTheme(VGroup currentGroup, Theme t) {
593            // We can do a tree traverse here, apply theme to all paths which
594            // can apply theme.
595            final ArrayList<VPath> paths = currentGroup.mPathList;
596            for (int j = paths.size() - 1; j >= 0; j--) {
597                final VPath path = paths.get(j);
598                if (path.canApplyTheme()) {
599                    path.applyTheme(t);
600                }
601            }
602
603            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
604
605            for (int i = 0; i < childGroups.size(); i++) {
606                VGroup childGroup = childGroups.get(i);
607                if (childGroup.canApplyTheme()) {
608                    childGroup.applyTheme(t);
609                }
610                recursiveApplyTheme(childGroup, t);
611            }
612
613        }
614
615        public void setColorFilter(ColorFilter colorFilter) {
616            mColorFilter = colorFilter;
617
618            if (mFillPaint != null) {
619                mFillPaint.setColorFilter(colorFilter);
620            }
621
622            if (mStrokePaint != null) {
623                mStrokePaint.setColorFilter(colorFilter);
624            }
625
626        }
627
628        private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
629                float currentAlpha, Canvas canvas, int w, int h) {
630            // Calculate current group's matrix by preConcat the parent's and
631            // and the current one on the top of the stack.
632            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
633            // Mi the local matrix at level i of the group tree.
634            currentGroup.mStackedMatrix.set(currentMatrix);
635
636            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
637
638            float stackedAlpha = currentAlpha * currentGroup.mGroupAlpha;
639            drawPath(currentGroup, stackedAlpha, canvas, w, h);
640            // Draw the group tree in post order.
641            for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
642                drawGroupTree(currentGroup.mChildGroupList.get(i),
643                        currentGroup.mStackedMatrix, stackedAlpha, canvas, w, h);
644            }
645        }
646
647        public void draw(Canvas canvas, int w, int h) {
648            // Travese the tree in pre-order to draw.
649            drawGroupTree(mRootGroup, IDENTITY_MATRIX, ((float) mRootAlpha) / 0xFF, canvas, w, h);
650        }
651
652        private void drawPath(VGroup vGroup, float stackedAlpha, Canvas canvas, int w, int h) {
653            final float scale = Math.min(h / mViewportHeight, w / mViewportWidth);
654
655            mFinalPathMatrix.set(vGroup.mStackedMatrix);
656            mFinalPathMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f);
657            mFinalPathMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
658
659            ArrayList<VPath> paths = vGroup.getPaths();
660            for (int i = 0; i < paths.size(); i++) {
661                VPath vPath = paths.get(i);
662                vPath.toPath(mPath);
663                final Path path = mPath;
664
665                if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
666                    float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
667                    float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
668
669                    if (mPathMeasure == null) {
670                        mPathMeasure = new PathMeasure();
671                    }
672                    mPathMeasure.setPath(mPath, false);
673
674                    float len = mPathMeasure.getLength();
675                    start = start * len;
676                    end = end * len;
677                    path.reset();
678                    if (start > end) {
679                        mPathMeasure.getSegment(start, len, path, true);
680                        mPathMeasure.getSegment(0f, end, path, true);
681                    } else {
682                        mPathMeasure.getSegment(start, end, path, true);
683                    }
684                    path.rLineTo(0, 0); // fix bug in measure
685                }
686
687                mRenderPath.reset();
688
689                mRenderPath.addPath(path, mFinalPathMatrix);
690
691                if (vPath.mClip) {
692                    canvas.clipPath(mRenderPath, Region.Op.REPLACE);
693                } else {
694                   if (vPath.mFillColor != 0) {
695                        if (mFillPaint == null) {
696                            mFillPaint = new Paint();
697                            mFillPaint.setColorFilter(mColorFilter);
698                            mFillPaint.setStyle(Paint.Style.FILL);
699                            mFillPaint.setAntiAlias(true);
700                        }
701                        mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha));
702                        canvas.drawPath(mRenderPath, mFillPaint);
703                    }
704
705                    if (vPath.mStrokeColor != 0) {
706                        if (mStrokePaint == null) {
707                            mStrokePaint = new Paint();
708                            mStrokePaint.setColorFilter(mColorFilter);
709                            mStrokePaint.setStyle(Paint.Style.STROKE);
710                            mStrokePaint.setAntiAlias(true);
711                        }
712
713                        final Paint strokePaint = mStrokePaint;
714                        if (vPath.mStrokeLineJoin != null) {
715                            strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
716                        }
717
718                        if (vPath.mStrokeLineCap != null) {
719                            strokePaint.setStrokeCap(vPath.mStrokeLineCap);
720                        }
721
722                        strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
723
724                        strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha));
725                        strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
726                        canvas.drawPath(mRenderPath, strokePaint);
727                    }
728                }
729            }
730        }
731
732        private void parseViewport(Resources r, AttributeSet attrs)
733                throws XmlPullParserException {
734            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableViewport);
735            mViewportWidth = a.getFloat(R.styleable.VectorDrawableViewport_viewportWidth, mViewportWidth);
736            mViewportHeight = a.getFloat(R.styleable.VectorDrawableViewport_viewportHeight, mViewportHeight);
737
738            if (mViewportWidth <= 0) {
739                throw new XmlPullParserException(a.getPositionDescription() +
740                        "<viewport> tag requires viewportWidth > 0");
741            } else if (mViewportHeight <= 0) {
742                throw new XmlPullParserException(a.getPositionDescription() +
743                        "<viewport> tag requires viewportHeight > 0");
744            }
745
746            a.recycle();
747        }
748
749        private void parseSize(Resources r, AttributeSet attrs)
750                throws XmlPullParserException  {
751            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize);
752            mBaseWidth = a.getDimension(R.styleable.VectorDrawableSize_width, mBaseWidth);
753            mBaseHeight = a.getDimension(R.styleable.VectorDrawableSize_height, mBaseHeight);
754
755            if (mBaseWidth <= 0) {
756                throw new XmlPullParserException(a.getPositionDescription() +
757                        "<size> tag requires width > 0");
758            } else if (mBaseHeight <= 0) {
759                throw new XmlPullParserException(a.getPositionDescription() +
760                        "<size> tag requires height > 0");
761            }
762
763            a.recycle();
764        }
765
766    }
767
768    static class VGroup {
769        private final ArrayList<VPath> mPathList = new ArrayList<VPath>();
770        private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>();
771
772        private float mRotate = 0;
773        private float mPivotX = 0;
774        private float mPivotY = 0;
775        private float mScaleX = 1;
776        private float mScaleY = 1;
777        private float mTranslateX = 0;
778        private float mTranslateY = 0;
779        private float mGroupAlpha = 1;
780
781        // mLocalMatrix is parsed from the XML.
782        private final Matrix mLocalMatrix = new Matrix();
783        // mStackedMatrix is only used when drawing, it combines all the
784        // parents' local matrices with the current one.
785        private final Matrix mStackedMatrix = new Matrix();
786
787        private int[] mThemeAttrs;
788
789        private String mGroupName = null;
790
791        /* Getter and Setter */
792        public float getRotation() {
793            return mRotate;
794        }
795
796        public void setRotation(float rotation) {
797            if (rotation != mRotate) {
798                mRotate = rotation;
799                updateLocalMatrix();
800            }
801        }
802
803        public float getPivotX() {
804            return mPivotX;
805        }
806
807        public void setPivotX(float pivotX) {
808            if (pivotX != mPivotX) {
809                mPivotX = pivotX;
810                updateLocalMatrix();
811            }
812        }
813
814        public float getPivotY() {
815            return mPivotY;
816        }
817
818        public void setPivotY(float pivotY) {
819            if (pivotY != mPivotY) {
820                mPivotY = pivotY;
821                updateLocalMatrix();
822            }
823        }
824
825        public float getScaleX() {
826            return mScaleX;
827        }
828
829        public void setScaleX(float scaleX) {
830            if (scaleX != mScaleX) {
831                mScaleX = scaleX;
832                updateLocalMatrix();
833            }
834        }
835
836        public float getScaleY() {
837            return mScaleY;
838        }
839
840        public void setScaleY(float scaleY) {
841            if (scaleY != mScaleY) {
842                mScaleY = scaleY;
843                updateLocalMatrix();
844            }
845        }
846
847        public float getTranslateX() {
848            return mTranslateX;
849        }
850
851        public void setTranslateX(float translateX) {
852            if (translateX != mTranslateX) {
853                mTranslateX = translateX;
854                updateLocalMatrix();
855            }
856        }
857
858        public float getTranslateY() {
859            return mTranslateY;
860        }
861
862        public void setTranslateY(float translateY) {
863            if (translateY != mTranslateY) {
864                mTranslateY = translateY;
865                updateLocalMatrix();
866            }
867        }
868
869        public float getAlpha() {
870            return mGroupAlpha;
871        }
872
873        public void setAlpha(float groupAlpha) {
874            if (groupAlpha != mGroupAlpha) {
875                mGroupAlpha = groupAlpha;
876            }
877        }
878
879        public String getGroupName() {
880            return mGroupName;
881        }
882
883        public Matrix getLocalMatrix() {
884            return mLocalMatrix;
885        }
886
887        public void add(VPath path) {
888            mPathList.add(path);
889         }
890
891        public boolean canApplyTheme() {
892            return mThemeAttrs != null;
893        }
894
895        public void applyTheme(Theme t) {
896            if (mThemeAttrs == null) {
897                return;
898            }
899
900            final TypedArray a = t.resolveAttributes(
901                    mThemeAttrs, R.styleable.VectorDrawablePath);
902
903            mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate);
904            mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX);
905            mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY);
906            mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX);
907            mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY);
908            mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX);
909            mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
910            mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha);
911            updateLocalMatrix();
912            if (a.hasValue(R.styleable.VectorDrawableGroup_name)) {
913                mGroupName = a.getString(R.styleable.VectorDrawableGroup_name);
914            }
915            a.recycle();
916        }
917
918        public void inflate(Resources res, AttributeSet attrs, Theme theme) {
919            final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawableGroup);
920            final int[] themeAttrs = a.extractThemeAttrs();
921
922            mThemeAttrs = themeAttrs;
923            // NOTE: The set of attributes loaded here MUST match the
924            // set of attributes loaded in applyTheme.
925
926            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_rotation] == 0) {
927                mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate);
928            }
929
930            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotX] == 0) {
931                mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX);
932            }
933
934            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotY] == 0) {
935                mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY);
936            }
937
938            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleX] == 0) {
939                mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX);
940            }
941
942            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleY] == 0) {
943                mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY);
944            }
945
946            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateX] == 0) {
947                mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX);
948            }
949
950            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateY] == 0) {
951                mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
952            }
953
954            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_name] == 0) {
955                mGroupName = a.getString(R.styleable.VectorDrawableGroup_name);
956            }
957
958            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_alpha] == 0) {
959                mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha);
960            }
961
962            updateLocalMatrix();
963            a.recycle();
964        }
965
966        private void updateLocalMatrix() {
967            // The order we apply is the same as the
968            // RenderNode.cpp::applyViewPropertyTransforms().
969            mLocalMatrix.reset();
970            mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
971            mLocalMatrix.postScale(mScaleX, mScaleY);
972            mLocalMatrix.postRotate(mRotate, 0, 0);
973            mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
974        }
975
976        /**
977         * Must return in order of adding
978         * @return ordered list of paths
979         */
980        public ArrayList<VPath> getPaths() {
981            return mPathList;
982        }
983
984    }
985
986    private static class VPath {
987        private int[] mThemeAttrs;
988
989        int mStrokeColor = 0;
990        float mStrokeWidth = 0;
991        float mStrokeOpacity = Float.NaN;
992        int mFillColor = Color.BLACK;
993        int mFillRule;
994        float mFillOpacity = Float.NaN;
995        float mTrimPathStart = 0;
996        float mTrimPathEnd = 1;
997        float mTrimPathOffset = 0;
998
999        boolean mClip = false;
1000        Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
1001        Paint.Join mStrokeLineJoin = Paint.Join.MITER;
1002        float mStrokeMiterlimit = 4;
1003
1004        private PathParser.PathDataNode[] mNode = null;
1005        private String mPathName;
1006
1007        public VPath() {
1008            // Empty constructor.
1009        }
1010
1011        public void toPath(Path path) {
1012            path.reset();
1013            if (mNode != null) {
1014                PathParser.PathDataNode.nodesToPath(mNode, path);
1015            }
1016        }
1017
1018        public String getPathName() {
1019            return mPathName;
1020        }
1021
1022        private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
1023            switch (id) {
1024                case LINECAP_BUTT:
1025                    return Paint.Cap.BUTT;
1026                case LINECAP_ROUND:
1027                    return Paint.Cap.ROUND;
1028                case LINECAP_SQUARE:
1029                    return Paint.Cap.SQUARE;
1030                default:
1031                    return defValue;
1032            }
1033        }
1034
1035        private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) {
1036            switch (id) {
1037                case LINEJOIN_MITER:
1038                    return Paint.Join.MITER;
1039                case LINEJOIN_ROUND:
1040                    return Paint.Join.ROUND;
1041                case LINEJOIN_BEVEL:
1042                    return Paint.Join.BEVEL;
1043                default:
1044                    return defValue;
1045            }
1046        }
1047
1048        /* Setters and Getters */
1049        int getStroke() {
1050            return mStrokeColor;
1051        }
1052
1053        void setStroke(int strokeColor) {
1054            mStrokeColor = strokeColor;
1055        }
1056
1057        float getStrokeWidth() {
1058            return mStrokeWidth;
1059        }
1060
1061        void setStrokeWidth(float strokeWidth) {
1062            mStrokeWidth = strokeWidth;
1063        }
1064
1065        float getStrokeOpacity() {
1066            return mStrokeOpacity;
1067        }
1068
1069        void setStrokeOpacity(float strokeOpacity) {
1070            mStrokeOpacity = strokeOpacity;
1071        }
1072
1073        int getFill() {
1074            return mFillColor;
1075        }
1076
1077        void setFill(int fillColor) {
1078            mFillColor = fillColor;
1079        }
1080
1081        float getFillOpacity() {
1082            return mFillOpacity;
1083        }
1084
1085        void setFillOpacity(float fillOpacity) {
1086            mFillOpacity = fillOpacity;
1087        }
1088
1089        float getTrimPathStart() {
1090            return mTrimPathStart;
1091        }
1092
1093        void setTrimPathStart(float trimPathStart) {
1094            mTrimPathStart = trimPathStart;
1095        }
1096
1097        float getTrimPathEnd() {
1098            return mTrimPathEnd;
1099        }
1100
1101        void setTrimPathEnd(float trimPathEnd) {
1102            mTrimPathEnd = trimPathEnd;
1103        }
1104
1105        float getTrimPathOffset() {
1106            return mTrimPathOffset;
1107        }
1108
1109        void setTrimPathOffset(float trimPathOffset) {
1110            mTrimPathOffset = trimPathOffset;
1111        }
1112
1113        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1114            final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawablePath);
1115            final int[] themeAttrs = a.extractThemeAttrs();
1116            mThemeAttrs = themeAttrs;
1117
1118            // NOTE: The set of attributes loaded here MUST match the
1119            // set of attributes loaded in applyTheme.
1120            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_clipToPath] == 0) {
1121                mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
1122            }
1123
1124            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_name] == 0) {
1125                mPathName = a.getString(R.styleable.VectorDrawablePath_name);
1126            }
1127
1128            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] == 0) {
1129                mNode = PathParser.createNodesFromPathData(a.getString(
1130                        R.styleable.VectorDrawablePath_pathData));
1131            }
1132
1133            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0) {
1134                mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
1135            }
1136
1137            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fillOpacity] == 0) {
1138                mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
1139            }
1140
1141            if (themeAttrs == null
1142                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineCap] == 0) {
1143                mStrokeLineCap = getStrokeLineCap(
1144                        a.getInt(R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
1145            }
1146
1147            if (themeAttrs == null
1148                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineJoin] == 0) {
1149                mStrokeLineJoin = getStrokeLineJoin(
1150                        a.getInt(R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
1151            }
1152
1153            if (themeAttrs == null
1154                    || themeAttrs[R.styleable.VectorDrawablePath_strokeMiterLimit] == 0) {
1155                mStrokeMiterlimit = a.getFloat(
1156                        R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
1157            }
1158
1159            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_stroke] == 0) {
1160                mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor);
1161            }
1162
1163            if (themeAttrs == null
1164                    || themeAttrs[R.styleable.VectorDrawablePath_strokeOpacity] == 0) {
1165                mStrokeOpacity = a.getFloat(
1166                        R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
1167            }
1168
1169            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_strokeWidth] == 0) {
1170                mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth);
1171            }
1172
1173            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_trimPathEnd] == 0) {
1174                mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd);
1175            }
1176
1177            if (themeAttrs == null
1178                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathOffset] == 0) {
1179                mTrimPathOffset = a.getFloat(
1180                        R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
1181            }
1182
1183            if (themeAttrs == null
1184                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathStart] == 0) {
1185                mTrimPathStart = a.getFloat(
1186                        R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
1187            }
1188
1189            updateColorAlphas();
1190
1191            a.recycle();
1192        }
1193
1194        public boolean canApplyTheme() {
1195            return mThemeAttrs != null;
1196        }
1197
1198        public void applyTheme(Theme t) {
1199            if (mThemeAttrs == null) {
1200                return;
1201            }
1202
1203            final TypedArray a = t.resolveAttributes(
1204                    mThemeAttrs, R.styleable.VectorDrawablePath);
1205
1206            mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
1207
1208            if (a.hasValue(R.styleable.VectorDrawablePath_name)) {
1209                mPathName = a.getString(R.styleable.VectorDrawablePath_name);
1210            }
1211
1212            if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) {
1213                mNode = PathParser.createNodesFromPathData(a.getString(
1214                        R.styleable.VectorDrawablePath_pathData));
1215            }
1216
1217            mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
1218            mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
1219
1220            mStrokeLineCap = getStrokeLineCap(a.getInt(
1221                    R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
1222            mStrokeLineJoin = getStrokeLineJoin(a.getInt(
1223                    R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
1224            mStrokeMiterlimit = a.getFloat(
1225                    R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
1226            mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor);
1227            mStrokeOpacity = a.getFloat(
1228                    R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
1229            mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth);
1230
1231            mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd);
1232            mTrimPathOffset = a.getFloat(
1233                    R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
1234            mTrimPathStart = a.getFloat(
1235                    R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
1236
1237            updateColorAlphas();
1238            a.recycle();
1239        }
1240
1241        private void updateColorAlphas() {
1242            if (!Float.isNaN(mFillOpacity)) {
1243                mFillColor = applyAlpha(mFillColor, mFillOpacity);
1244            }
1245
1246            if (!Float.isNaN(mStrokeOpacity)) {
1247                mStrokeColor = applyAlpha(mStrokeColor, mStrokeOpacity);
1248            }
1249        }
1250    }
1251}
1252