VectorDrawable.java revision 02aefd779e6b4077a62ca4819499a01771837945
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 boolean canApplyTheme() {
290        return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme();
291    }
292
293    @Override
294    public void applyTheme(Theme t) {
295        super.applyTheme(t);
296
297        final VectorDrawableState state = mVectorState;
298        final VPathRenderer path = state.mVPathRenderer;
299        if (path != null && path.canApplyTheme()) {
300            path.applyTheme(t);
301        }
302    }
303
304    /** @hide */
305    public static VectorDrawable create(Resources resources, int rid) {
306        try {
307            final XmlPullParser xpp = resources.getXml(rid);
308            final AttributeSet attrs = Xml.asAttributeSet(xpp);
309            final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
310            factory.setNamespaceAware(true);
311
312            final VectorDrawable drawable = new VectorDrawable();
313            drawable.inflate(resources, xpp, attrs);
314
315            return drawable;
316        } catch (XmlPullParserException e) {
317            Log.e(LOGTAG, "parser error", e);
318        } catch (IOException e) {
319            Log.e(LOGTAG, "parser error", e);
320        }
321        return null;
322    }
323
324    private static int applyAlpha(int color, float alpha) {
325        int alphaBytes = Color.alpha(color);
326        color &= 0x00FFFFFF;
327        color |= ((int) (alphaBytes * alpha)) << 24;
328        return color;
329    }
330
331
332    @Override
333    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
334            throws XmlPullParserException, IOException {
335        final TypedArray a = obtainAttributes(res, theme,  attrs,R.styleable.VectorDrawable);
336        updateStateFromTypedArray(a);
337        a.recycle();
338
339        final VectorDrawableState state = mVectorState;
340        state.mVPathRenderer = inflateInternal(res, parser, attrs, theme);
341
342        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
343        state.mVPathRenderer.setColorFilter(mTintFilter);
344    }
345
346    private void updateStateFromTypedArray(TypedArray a) {
347        final VectorDrawableState state = mVectorState;
348
349        // Extract the theme attributes, if any.
350        state.mThemeAttrs = a.extractThemeAttrs();
351
352        final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
353        if (tintMode != -1) {
354            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
355        }
356
357        final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
358        if (tint != null) {
359            state.mTint = tint;
360        }
361    }
362
363    private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
364            throws XmlPullParserException, IOException {
365        final VPathRenderer pathRenderer = new VPathRenderer();
366
367        boolean noSizeTag = true;
368        boolean noViewportTag = true;
369        boolean noPathTag = true;
370
371        // Use a stack to help to build the group tree.
372        // The top of the stack is always the current group.
373        final Stack<VGroup> groupStack = new Stack<VGroup>();
374        groupStack.push(pathRenderer.mRootGroup);
375
376        int eventType = parser.getEventType();
377        while (eventType != XmlPullParser.END_DOCUMENT) {
378            if (eventType == XmlPullParser.START_TAG) {
379                final String tagName = parser.getName();
380                final VGroup currentGroup = groupStack.peek();
381
382                if (SHAPE_PATH.equals(tagName)) {
383                    final VPath path = new VPath();
384                    path.inflate(res, attrs, theme);
385                    currentGroup.add(path);
386                    if (path.getPathName() != null) {
387                        mVGTargetsMap.put(path.getPathName(), path);
388                    }
389                    noPathTag = false;
390                } else if (SHAPE_SIZE.equals(tagName)) {
391                    pathRenderer.parseSize(res, attrs);
392                    noSizeTag = false;
393                } else if (SHAPE_VIEWPORT.equals(tagName)) {
394                    pathRenderer.parseViewport(res, attrs);
395                    noViewportTag = false;
396                } else if (SHAPE_GROUP.equals(tagName)) {
397                    VGroup newChildGroup = new VGroup();
398                    newChildGroup.inflate(res, attrs, theme);
399                    currentGroup.mChildGroupList.add(newChildGroup);
400                    groupStack.push(newChildGroup);
401                    if (newChildGroup.getGroupName() != null) {
402                        mVGTargetsMap.put(newChildGroup.getGroupName(), newChildGroup);
403                    }
404                }
405            } else if (eventType == XmlPullParser.END_TAG) {
406                final String tagName = parser.getName();
407                if (SHAPE_GROUP.equals(tagName)) {
408                    groupStack.pop();
409                }
410            }
411            eventType = parser.next();
412        }
413
414        // Print the tree out for debug.
415        if (DBG_VECTOR_DRAWABLE) {
416            printGroupTree(pathRenderer.mRootGroup, 0);
417        }
418
419        if (noSizeTag || noViewportTag || noPathTag) {
420            final StringBuffer tag = new StringBuffer();
421
422            if (noSizeTag) {
423                tag.append(SHAPE_SIZE);
424            }
425
426            if (noViewportTag) {
427                if (tag.length() > 0) {
428                    tag.append(" & ");
429                }
430                tag.append(SHAPE_SIZE);
431            }
432
433            if (noPathTag) {
434                if (tag.length() > 0) {
435                    tag.append(" or ");
436                }
437                tag.append(SHAPE_PATH);
438            }
439
440            throw new XmlPullParserException("no " + tag + " defined");
441        }
442
443        return pathRenderer;
444    }
445
446    private void printGroupTree(VGroup currentGroup, int level) {
447        String indent = "";
448        for (int i = 0 ; i < level ; i++) {
449            indent += "    ";
450        }
451        // Print the current node
452        Log.v(LOGTAG, indent + "current group is :" +  currentGroup.getGroupName()
453                + " rotation is " + currentGroup.mRotate);
454        Log.v(LOGTAG, indent + "matrix is :" +  currentGroup.getLocalMatrix().toString());
455        // Then print all the children
456        for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
457            printGroupTree(currentGroup.mChildGroupList.get(i), level + 1);
458        }
459    }
460
461    private static class VectorDrawableState extends ConstantState {
462        int[] mThemeAttrs;
463        int mChangingConfigurations;
464        VPathRenderer mVPathRenderer;
465        Rect mPadding;
466        ColorStateList mTint;
467        Mode mTintMode;
468
469        public VectorDrawableState(VectorDrawableState copy) {
470            if (copy != null) {
471                mThemeAttrs = copy.mThemeAttrs;
472                mChangingConfigurations = copy.mChangingConfigurations;
473                // TODO: Make sure the constant state are handled correctly.
474                mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
475                mPadding = new Rect(copy.mPadding);
476                mTint = copy.mTint;
477                mTintMode = copy.mTintMode;
478            }
479        }
480
481        @Override
482        public Drawable newDrawable() {
483            return new VectorDrawable(this, null, null);
484        }
485
486        @Override
487        public Drawable newDrawable(Resources res) {
488            return new VectorDrawable(this, res, null);
489        }
490
491        @Override
492        public Drawable newDrawable(Resources res, Theme theme) {
493            return new VectorDrawable(this, res, theme);
494        }
495
496        @Override
497        public int getChangingConfigurations() {
498            return mChangingConfigurations;
499        }
500    }
501
502    private static class VPathRenderer {
503        /* Right now the internal data structure is organized as a tree.
504         * Each node can be a group node, or a path.
505         * A group node can have groups or paths as children, but a path node has
506         * no children.
507         * One example can be:
508         *                 Root Group
509         *                /    |     \
510         *           Group    Path    Group
511         *          /     \             |
512         *         Path   Path         Path
513         *
514         */
515        private final VGroup mRootGroup;
516
517        private final Path mPath = new Path();
518        private final Path mRenderPath = new Path();
519        private static final Matrix IDENTITY_MATRIX = new Matrix();
520
521        private Paint mStrokePaint;
522        private Paint mFillPaint;
523        private ColorFilter mColorFilter;
524        private PathMeasure mPathMeasure;
525
526        private float mBaseWidth = 0;
527        private float mBaseHeight = 0;
528        private float mViewportWidth = 0;
529        private float mViewportHeight = 0;
530        private int mRootAlpha = 0xFF;
531
532        private final Matrix mFinalPathMatrix = new Matrix();
533
534        public VPathRenderer() {
535            mRootGroup = new VGroup();
536        }
537
538        public void setRootAlpha(int alpha) {
539            mRootAlpha = alpha;
540        }
541
542        public int getRootAlpha() {
543            return mRootAlpha;
544        }
545
546        public VPathRenderer(VPathRenderer copy) {
547            mRootGroup = copy.mRootGroup;
548            mBaseWidth = copy.mBaseWidth;
549            mBaseHeight = copy.mBaseHeight;
550            mViewportWidth = copy.mViewportHeight;
551            mViewportHeight = copy.mViewportHeight;
552        }
553
554        public boolean canApplyTheme() {
555            // If one of the paths can apply theme, then return true;
556            return recursiveCanApplyTheme(mRootGroup);
557        }
558
559        private boolean recursiveCanApplyTheme(VGroup currentGroup) {
560            // We can do a tree traverse here, if there is one path return true,
561            // then we return true for the whole tree.
562            final ArrayList<VPath> paths = currentGroup.mPathList;
563            for (int j = paths.size() - 1; j >= 0; j--) {
564                final VPath path = paths.get(j);
565                if (path.canApplyTheme()) {
566                    return true;
567                }
568            }
569
570            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
571
572            for (int i = 0; i < childGroups.size(); i++) {
573                VGroup childGroup = childGroups.get(i);
574                if (childGroup.canApplyTheme()
575                        || recursiveCanApplyTheme(childGroup)) {
576                    return true;
577                }
578            }
579            return false;
580        }
581
582        public void applyTheme(Theme t) {
583            // Apply theme to every path of the tree.
584            recursiveApplyTheme(mRootGroup, t);
585        }
586
587        private void recursiveApplyTheme(VGroup currentGroup, Theme t) {
588            // We can do a tree traverse here, apply theme to all paths which
589            // can apply theme.
590            final ArrayList<VPath> paths = currentGroup.mPathList;
591            for (int j = paths.size() - 1; j >= 0; j--) {
592                final VPath path = paths.get(j);
593                if (path.canApplyTheme()) {
594                    path.applyTheme(t);
595                }
596            }
597
598            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
599
600            for (int i = 0; i < childGroups.size(); i++) {
601                VGroup childGroup = childGroups.get(i);
602                if (childGroup.canApplyTheme()) {
603                    childGroup.applyTheme(t);
604                }
605                recursiveApplyTheme(childGroup, t);
606            }
607
608        }
609
610        public void setColorFilter(ColorFilter colorFilter) {
611            mColorFilter = colorFilter;
612
613            if (mFillPaint != null) {
614                mFillPaint.setColorFilter(colorFilter);
615            }
616
617            if (mStrokePaint != null) {
618                mStrokePaint.setColorFilter(colorFilter);
619            }
620
621        }
622
623        private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
624                float currentAlpha, Canvas canvas, int w, int h) {
625            // Calculate current group's matrix by preConcat the parent's and
626            // and the current one on the top of the stack.
627            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
628            // Mi the local matrix at level i of the group tree.
629            currentGroup.mStackedMatrix.set(currentMatrix);
630
631            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
632
633            float stackedAlpha = currentAlpha * currentGroup.mGroupAlpha;
634            drawPath(currentGroup, stackedAlpha, canvas, w, h);
635            // Draw the group tree in post order.
636            for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
637                drawGroupTree(currentGroup.mChildGroupList.get(i),
638                        currentGroup.mStackedMatrix, stackedAlpha, canvas, w, h);
639            }
640        }
641
642        public void draw(Canvas canvas, int w, int h) {
643            // Travese the tree in pre-order to draw.
644            drawGroupTree(mRootGroup, IDENTITY_MATRIX, ((float) mRootAlpha) / 0xFF, canvas, w, h);
645        }
646
647        private void drawPath(VGroup vGroup, float stackedAlpha, Canvas canvas, int w, int h) {
648            final float scale = Math.min(h / mViewportHeight, w / mViewportWidth);
649
650            mFinalPathMatrix.set(vGroup.mStackedMatrix);
651            mFinalPathMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f);
652            mFinalPathMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
653
654            ArrayList<VPath> paths = vGroup.getPaths();
655            for (int i = 0; i < paths.size(); i++) {
656                VPath vPath = paths.get(i);
657                vPath.toPath(mPath);
658                final Path path = mPath;
659
660                if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
661                    float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
662                    float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
663
664                    if (mPathMeasure == null) {
665                        mPathMeasure = new PathMeasure();
666                    }
667                    mPathMeasure.setPath(mPath, false);
668
669                    float len = mPathMeasure.getLength();
670                    start = start * len;
671                    end = end * len;
672                    path.reset();
673                    if (start > end) {
674                        mPathMeasure.getSegment(start, len, path, true);
675                        mPathMeasure.getSegment(0f, end, path, true);
676                    } else {
677                        mPathMeasure.getSegment(start, end, path, true);
678                    }
679                    path.rLineTo(0, 0); // fix bug in measure
680                }
681
682                mRenderPath.reset();
683
684                mRenderPath.addPath(path, mFinalPathMatrix);
685
686                if (vPath.mClip) {
687                    canvas.clipPath(mRenderPath, Region.Op.REPLACE);
688                } else {
689                   if (vPath.mFillColor != 0) {
690                        if (mFillPaint == null) {
691                            mFillPaint = new Paint();
692                            mFillPaint.setColorFilter(mColorFilter);
693                            mFillPaint.setStyle(Paint.Style.FILL);
694                            mFillPaint.setAntiAlias(true);
695                        }
696                        mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha));
697                        canvas.drawPath(mRenderPath, mFillPaint);
698                    }
699
700                    if (vPath.mStrokeColor != 0) {
701                        if (mStrokePaint == null) {
702                            mStrokePaint = new Paint();
703                            mStrokePaint.setColorFilter(mColorFilter);
704                            mStrokePaint.setStyle(Paint.Style.STROKE);
705                            mStrokePaint.setAntiAlias(true);
706                        }
707
708                        final Paint strokePaint = mStrokePaint;
709                        if (vPath.mStrokeLineJoin != null) {
710                            strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
711                        }
712
713                        if (vPath.mStrokeLineCap != null) {
714                            strokePaint.setStrokeCap(vPath.mStrokeLineCap);
715                        }
716
717                        strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
718
719                        strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha));
720                        strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
721                        canvas.drawPath(mRenderPath, strokePaint);
722                    }
723                }
724            }
725        }
726
727        private void parseViewport(Resources r, AttributeSet attrs)
728                throws XmlPullParserException {
729            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableViewport);
730            mViewportWidth = a.getFloat(R.styleable.VectorDrawableViewport_viewportWidth, mViewportWidth);
731            mViewportHeight = a.getFloat(R.styleable.VectorDrawableViewport_viewportHeight, mViewportHeight);
732
733            if (mViewportWidth <= 0) {
734                throw new XmlPullParserException(a.getPositionDescription() +
735                        "<viewport> tag requires viewportWidth > 0");
736            } else if (mViewportHeight <= 0) {
737                throw new XmlPullParserException(a.getPositionDescription() +
738                        "<viewport> tag requires viewportHeight > 0");
739            }
740
741            a.recycle();
742        }
743
744        private void parseSize(Resources r, AttributeSet attrs)
745                throws XmlPullParserException  {
746            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize);
747            mBaseWidth = a.getDimension(R.styleable.VectorDrawableSize_width, mBaseWidth);
748            mBaseHeight = a.getDimension(R.styleable.VectorDrawableSize_height, mBaseHeight);
749
750            if (mBaseWidth <= 0) {
751                throw new XmlPullParserException(a.getPositionDescription() +
752                        "<size> tag requires width > 0");
753            } else if (mBaseHeight <= 0) {
754                throw new XmlPullParserException(a.getPositionDescription() +
755                        "<size> tag requires height > 0");
756            }
757
758            a.recycle();
759        }
760
761    }
762
763    static class VGroup {
764        private final ArrayList<VPath> mPathList = new ArrayList<VPath>();
765        private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>();
766
767        private float mRotate = 0;
768        private float mPivotX = 0;
769        private float mPivotY = 0;
770        private float mScaleX = 1;
771        private float mScaleY = 1;
772        private float mTranslateX = 0;
773        private float mTranslateY = 0;
774        private float mGroupAlpha = 1;
775
776        // mLocalMatrix is parsed from the XML.
777        private final Matrix mLocalMatrix = new Matrix();
778        // mStackedMatrix is only used when drawing, it combines all the
779        // parents' local matrices with the current one.
780        private final Matrix mStackedMatrix = new Matrix();
781
782        private int[] mThemeAttrs;
783
784        private String mGroupName = null;
785
786        /* Getter and Setter */
787        public float getRotation() {
788            return mRotate;
789        }
790
791        public void setRotation(float rotation) {
792            if (rotation != mRotate) {
793                mRotate = rotation;
794                updateLocalMatrix();
795            }
796        }
797
798        public float getPivotX() {
799            return mPivotX;
800        }
801
802        public void setPivotX(float pivotX) {
803            if (pivotX != mPivotX) {
804                mPivotX = pivotX;
805                updateLocalMatrix();
806            }
807        }
808
809        public float getPivotY() {
810            return mPivotY;
811        }
812
813        public void setPivotY(float pivotY) {
814            if (pivotY != mPivotY) {
815                mPivotY = pivotY;
816                updateLocalMatrix();
817            }
818        }
819
820        public float getScaleX() {
821            return mScaleX;
822        }
823
824        public void setScaleX(float scaleX) {
825            if (scaleX != mScaleX) {
826                mScaleX = scaleX;
827                updateLocalMatrix();
828            }
829        }
830
831        public float getScaleY() {
832            return mScaleY;
833        }
834
835        public void setScaleY(float scaleY) {
836            if (scaleY != mScaleY) {
837                mScaleY = scaleY;
838                updateLocalMatrix();
839            }
840        }
841
842        public float getTranslateX() {
843            return mTranslateX;
844        }
845
846        public void setTranslateX(float translateX) {
847            if (translateX != mTranslateX) {
848                mTranslateX = translateX;
849                updateLocalMatrix();
850            }
851        }
852
853        public float getTranslateY() {
854            return mTranslateY;
855        }
856
857        public void setTranslateY(float translateY) {
858            if (translateY != mTranslateY) {
859                mTranslateY = translateY;
860                updateLocalMatrix();
861            }
862        }
863
864        public float getAlpha() {
865            return mGroupAlpha;
866        }
867
868        public void setAlpha(float groupAlpha) {
869            if (groupAlpha != mGroupAlpha) {
870                mGroupAlpha = groupAlpha;
871            }
872        }
873
874        public String getGroupName() {
875            return mGroupName;
876        }
877
878        public Matrix getLocalMatrix() {
879            return mLocalMatrix;
880        }
881
882        public void add(VPath path) {
883            mPathList.add(path);
884         }
885
886        public boolean canApplyTheme() {
887            return mThemeAttrs != null;
888        }
889
890        public void applyTheme(Theme t) {
891            if (mThemeAttrs == null) {
892                return;
893            }
894
895            final TypedArray a = t.resolveAttributes(
896                    mThemeAttrs, R.styleable.VectorDrawablePath);
897
898            mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate);
899            mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX);
900            mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY);
901            mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX);
902            mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY);
903            mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX);
904            mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
905            mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha);
906            updateLocalMatrix();
907            if (a.hasValue(R.styleable.VectorDrawableGroup_name)) {
908                mGroupName = a.getString(R.styleable.VectorDrawableGroup_name);
909            }
910            a.recycle();
911        }
912
913        public void inflate(Resources res, AttributeSet attrs, Theme theme) {
914            final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawableGroup);
915            final int[] themeAttrs = a.extractThemeAttrs();
916
917            mThemeAttrs = themeAttrs;
918            // NOTE: The set of attributes loaded here MUST match the
919            // set of attributes loaded in applyTheme.
920
921            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_rotation] == 0) {
922                mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate);
923            }
924
925            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotX] == 0) {
926                mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX);
927            }
928
929            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotY] == 0) {
930                mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY);
931            }
932
933            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleX] == 0) {
934                mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX);
935            }
936
937            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleY] == 0) {
938                mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY);
939            }
940
941            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateX] == 0) {
942                mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX);
943            }
944
945            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateY] == 0) {
946                mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
947            }
948
949            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_name] == 0) {
950                mGroupName = a.getString(R.styleable.VectorDrawableGroup_name);
951            }
952
953            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_alpha] == 0) {
954                mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha);
955            }
956
957            updateLocalMatrix();
958            a.recycle();
959        }
960
961        private void updateLocalMatrix() {
962            // The order we apply is the same as the
963            // RenderNode.cpp::applyViewPropertyTransforms().
964            mLocalMatrix.reset();
965            mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
966            mLocalMatrix.postScale(mScaleX, mScaleY);
967            mLocalMatrix.postRotate(mRotate, 0, 0);
968            mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
969        }
970
971        /**
972         * Must return in order of adding
973         * @return ordered list of paths
974         */
975        public ArrayList<VPath> getPaths() {
976            return mPathList;
977        }
978
979    }
980
981    private static class VPath {
982        private int[] mThemeAttrs;
983
984        int mStrokeColor = 0;
985        float mStrokeWidth = 0;
986        float mStrokeOpacity = Float.NaN;
987        int mFillColor = Color.BLACK;
988        int mFillRule;
989        float mFillOpacity = Float.NaN;
990        float mTrimPathStart = 0;
991        float mTrimPathEnd = 1;
992        float mTrimPathOffset = 0;
993
994        boolean mClip = false;
995        Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
996        Paint.Join mStrokeLineJoin = Paint.Join.MITER;
997        float mStrokeMiterlimit = 4;
998
999        private PathParser.PathDataNode[] mNode = null;
1000        private String mPathName;
1001
1002        public VPath() {
1003            // Empty constructor.
1004        }
1005
1006        public void toPath(Path path) {
1007            path.reset();
1008            if (mNode != null) {
1009                PathParser.PathDataNode.nodesToPath(mNode, path);
1010            }
1011        }
1012
1013        public String getPathName() {
1014            return mPathName;
1015        }
1016
1017        private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
1018            switch (id) {
1019                case LINECAP_BUTT:
1020                    return Paint.Cap.BUTT;
1021                case LINECAP_ROUND:
1022                    return Paint.Cap.ROUND;
1023                case LINECAP_SQUARE:
1024                    return Paint.Cap.SQUARE;
1025                default:
1026                    return defValue;
1027            }
1028        }
1029
1030        private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) {
1031            switch (id) {
1032                case LINEJOIN_MITER:
1033                    return Paint.Join.MITER;
1034                case LINEJOIN_ROUND:
1035                    return Paint.Join.ROUND;
1036                case LINEJOIN_BEVEL:
1037                    return Paint.Join.BEVEL;
1038                default:
1039                    return defValue;
1040            }
1041        }
1042
1043        /* Setters and Getters, mostly used by animator from AnimatedVectorDrawable. */
1044        @SuppressWarnings("unused")
1045        public PathParser.PathDataNode[] getPathData() {
1046            return mNode;
1047        }
1048
1049        @SuppressWarnings("unused")
1050        public void setPathData(PathParser.PathDataNode[] node) {
1051            if (!PathParser.canMorph(mNode, node)) {
1052                // This should not happen in the middle of animation.
1053                mNode = PathParser.deepCopyNodes(node);
1054            } else {
1055                PathParser.updateNodes(mNode, node);
1056            }
1057        }
1058
1059        @SuppressWarnings("unused")
1060        int getStroke() {
1061            return mStrokeColor;
1062        }
1063
1064        @SuppressWarnings("unused")
1065        void setStroke(int strokeColor) {
1066            mStrokeColor = strokeColor;
1067        }
1068
1069        @SuppressWarnings("unused")
1070        float getStrokeWidth() {
1071            return mStrokeWidth;
1072        }
1073
1074        @SuppressWarnings("unused")
1075        void setStrokeWidth(float strokeWidth) {
1076            mStrokeWidth = strokeWidth;
1077        }
1078
1079        @SuppressWarnings("unused")
1080        float getStrokeOpacity() {
1081            return mStrokeOpacity;
1082        }
1083
1084        @SuppressWarnings("unused")
1085        void setStrokeOpacity(float strokeOpacity) {
1086            mStrokeOpacity = strokeOpacity;
1087        }
1088
1089        @SuppressWarnings("unused")
1090        int getFill() {
1091            return mFillColor;
1092        }
1093
1094        @SuppressWarnings("unused")
1095        void setFill(int fillColor) {
1096            mFillColor = fillColor;
1097        }
1098
1099        @SuppressWarnings("unused")
1100        float getFillOpacity() {
1101            return mFillOpacity;
1102        }
1103
1104        @SuppressWarnings("unused")
1105        void setFillOpacity(float fillOpacity) {
1106            mFillOpacity = fillOpacity;
1107        }
1108
1109        @SuppressWarnings("unused")
1110        float getTrimPathStart() {
1111            return mTrimPathStart;
1112        }
1113
1114        @SuppressWarnings("unused")
1115        void setTrimPathStart(float trimPathStart) {
1116            mTrimPathStart = trimPathStart;
1117        }
1118
1119        @SuppressWarnings("unused")
1120        float getTrimPathEnd() {
1121            return mTrimPathEnd;
1122        }
1123
1124        @SuppressWarnings("unused")
1125        void setTrimPathEnd(float trimPathEnd) {
1126            mTrimPathEnd = trimPathEnd;
1127        }
1128
1129        @SuppressWarnings("unused")
1130        float getTrimPathOffset() {
1131            return mTrimPathOffset;
1132        }
1133
1134        @SuppressWarnings("unused")
1135        void setTrimPathOffset(float trimPathOffset) {
1136            mTrimPathOffset = trimPathOffset;
1137        }
1138
1139        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1140            final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawablePath);
1141            final int[] themeAttrs = a.extractThemeAttrs();
1142            mThemeAttrs = themeAttrs;
1143
1144            // NOTE: The set of attributes loaded here MUST match the
1145            // set of attributes loaded in applyTheme.
1146            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_clipToPath] == 0) {
1147                mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
1148            }
1149
1150            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_name] == 0) {
1151                mPathName = a.getString(R.styleable.VectorDrawablePath_name);
1152            }
1153
1154            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] == 0) {
1155                mNode = PathParser.createNodesFromPathData(a.getString(
1156                        R.styleable.VectorDrawablePath_pathData));
1157            }
1158
1159            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0) {
1160                mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
1161            }
1162
1163            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fillOpacity] == 0) {
1164                mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
1165            }
1166
1167            if (themeAttrs == null
1168                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineCap] == 0) {
1169                mStrokeLineCap = getStrokeLineCap(
1170                        a.getInt(R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
1171            }
1172
1173            if (themeAttrs == null
1174                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineJoin] == 0) {
1175                mStrokeLineJoin = getStrokeLineJoin(
1176                        a.getInt(R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
1177            }
1178
1179            if (themeAttrs == null
1180                    || themeAttrs[R.styleable.VectorDrawablePath_strokeMiterLimit] == 0) {
1181                mStrokeMiterlimit = a.getFloat(
1182                        R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
1183            }
1184
1185            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_stroke] == 0) {
1186                mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor);
1187            }
1188
1189            if (themeAttrs == null
1190                    || themeAttrs[R.styleable.VectorDrawablePath_strokeOpacity] == 0) {
1191                mStrokeOpacity = a.getFloat(
1192                        R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
1193            }
1194
1195            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_strokeWidth] == 0) {
1196                mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth);
1197            }
1198
1199            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_trimPathEnd] == 0) {
1200                mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd);
1201            }
1202
1203            if (themeAttrs == null
1204                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathOffset] == 0) {
1205                mTrimPathOffset = a.getFloat(
1206                        R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
1207            }
1208
1209            if (themeAttrs == null
1210                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathStart] == 0) {
1211                mTrimPathStart = a.getFloat(
1212                        R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
1213            }
1214
1215            updateColorAlphas();
1216
1217            a.recycle();
1218        }
1219
1220        public boolean canApplyTheme() {
1221            return mThemeAttrs != null;
1222        }
1223
1224        public void applyTheme(Theme t) {
1225            if (mThemeAttrs == null) {
1226                return;
1227            }
1228
1229            final TypedArray a = t.resolveAttributes(
1230                    mThemeAttrs, R.styleable.VectorDrawablePath);
1231
1232            mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
1233
1234            if (a.hasValue(R.styleable.VectorDrawablePath_name)) {
1235                mPathName = a.getString(R.styleable.VectorDrawablePath_name);
1236            }
1237
1238            if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) {
1239                mNode = PathParser.createNodesFromPathData(a.getString(
1240                        R.styleable.VectorDrawablePath_pathData));
1241            }
1242
1243            mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
1244            mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
1245
1246            mStrokeLineCap = getStrokeLineCap(a.getInt(
1247                    R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
1248            mStrokeLineJoin = getStrokeLineJoin(a.getInt(
1249                    R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
1250            mStrokeMiterlimit = a.getFloat(
1251                    R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
1252            mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor);
1253            mStrokeOpacity = a.getFloat(
1254                    R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
1255            mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth);
1256
1257            mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd);
1258            mTrimPathOffset = a.getFloat(
1259                    R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
1260            mTrimPathStart = a.getFloat(
1261                    R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
1262
1263            updateColorAlphas();
1264            a.recycle();
1265        }
1266
1267        private void updateColorAlphas() {
1268            if (!Float.isNaN(mFillOpacity)) {
1269                mFillColor = applyAlpha(mFillColor, mFillOpacity);
1270            }
1271
1272            if (!Float.isNaN(mStrokeOpacity)) {
1273                mStrokeColor = applyAlpha(mStrokeColor, mStrokeOpacity);
1274            }
1275        }
1276    }
1277}
1278