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