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