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