VectorDrawable.java revision b7acab634ad2a8f318e5e53267df010e3c2a2219
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.annotation.NonNull;
18import android.content.res.ColorStateList;
19import android.content.res.Resources;
20import android.content.res.Resources.Theme;
21import android.content.res.TypedArray;
22import android.graphics.Bitmap;
23import android.graphics.Canvas;
24import android.graphics.Color;
25import android.graphics.ColorFilter;
26import android.graphics.Matrix;
27import android.graphics.Paint;
28import android.graphics.Path;
29import android.graphics.PathMeasure;
30import android.graphics.PixelFormat;
31import android.graphics.PorterDuffColorFilter;
32import android.graphics.Rect;
33import android.graphics.Region;
34import android.graphics.PorterDuff.Mode;
35import android.util.ArrayMap;
36import android.util.AttributeSet;
37import android.util.LayoutDirection;
38import android.util.Log;
39import android.util.MathUtils;
40import android.util.PathParser;
41import android.util.Xml;
42
43import com.android.internal.R;
44
45import org.xmlpull.v1.XmlPullParser;
46import org.xmlpull.v1.XmlPullParserException;
47
48import java.io.IOException;
49import java.util.ArrayList;
50import java.util.Stack;
51
52/**
53 * This lets you create a drawable based on an XML vector graphic. It can be
54 * defined in an XML file with the <code>&lt;vector></code> element.
55 * <p/>
56 * The vector drawable has the following elements:
57 * <p/>
58 * <dt><code>&lt;vector></code></dt>
59 * <dl>
60 * <dd>Used to define a vector drawable
61 * <dl>
62 * <dt><code>android:name</code></dt>
63 * <dd>Defines the name of this vector drawable.</dd>
64 * <dt><code>android:width</code></dt>
65 * <dd>Used to define the intrinsic width of the drawable.
66 * This support all the dimension units, normally specified with dp.</dd>
67 * <dt><code>android:height</code></dt>
68 * <dd>Used to define the intrinsic height the drawable.
69 * This support all the dimension units, normally specified with dp.</dd>
70 * <dt><code>android:viewportWidth</code></dt>
71 * <dd>Used to define the width of the viewport space. Viewport is basically
72 * the virtual canvas where the paths are drawn on.</dd>
73 * <dt><code>android:viewportHeight</code></dt>
74 * <dd>Used to define the height of the viewport space. Viewport is basically
75 * the virtual canvas where the paths are drawn on.</dd>
76 * <dt><code>android:tint</code></dt>
77 * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd>
78 * <dt><code>android:tintMode</code></dt>
79 * <dd>The Porter-Duff blending mode for the tint color. The default value is src_in.</dd>
80 * <dt><code>android:autoMirrored</code></dt>
81 * <dd>Indicates if the drawable needs to be mirrored when its layout direction is
82 * RTL (right-to-left).</dd>
83 * <dt><code>android:alpha</code></dt>
84 * <dd>The opacity of this drawable.</dd>
85 * </dl></dd>
86 * </dl>
87 *
88 * <dl>
89 * <dt><code>&lt;group></code></dt>
90 * <dd>Defines a group of paths or subgroups, plus transformation information.
91 * The transformations are defined in the same coordinates as the viewport.
92 * And the transformations are applied in the order of scale, rotate then translate.
93 * <dl>
94 * <dt><code>android:name</code></dt>
95 * <dd>Defines the name of the group.</dd>
96 * <dt><code>android:rotation</code></dt>
97 * <dd>The degrees of rotation of the group.</dd>
98 * <dt><code>android:pivotX</code></dt>
99 * <dd>The X coordinate of the pivot for the scale and rotation of the group.
100 * This is defined in the viewport space.</dd>
101 * <dt><code>android:pivotY</code></dt>
102 * <dd>The Y coordinate of the pivot for the scale and rotation of the group.
103 * This is defined in the viewport space.</dd>
104 * <dt><code>android:scaleX</code></dt>
105 * <dd>The amount of scale on the X Coordinate.</dd>
106 * <dt><code>android:scaleY</code></dt>
107 * <dd>The amount of scale on the Y coordinate.</dd>
108 * <dt><code>android:translateX</code></dt>
109 * <dd>The amount of translation on the X coordinate.
110 * This is defined in the viewport space.</dd>
111 * <dt><code>android:translateY</code></dt>
112 * <dd>The amount of translation on the Y coordinate.
113 * This is defined in the viewport space.</dd>
114 * </dl></dd>
115 * </dl>
116 *
117 * <dl>
118 * <dt><code>&lt;path></code></dt>
119 * <dd>Defines paths to be drawn.
120 * <dl>
121 * <dt><code>android:name</code></dt>
122 * <dd>Defines the name of the path.</dd>
123 * <dt><code>android:pathData</code></dt>
124 * <dd>Defines path data using exactly same format as "d" attribute
125 * in the SVG's path data. This is defined in the viewport space.</dd>
126 * <dt><code>android:fillColor</code></dt>
127 * <dd>Defines the color to fill the path (none if not present).</dd>
128 * <dt><code>android:strokeColor</code></dt>
129 * <dd>Defines the color to draw the path outline (none if not present).</dd>
130 * <dt><code>android:strokeWidth</code></dt>
131 * <dd>The width a path stroke.</dd>
132 * <dt><code>android:strokeAlpha</code></dt>
133 * <dd>The opacity of a path stroke.</dd>
134 * <dt><code>android:fillAlpha</code></dt>
135 * <dd>The opacity to fill the path with.</dd>
136 * <dt><code>android:trimPathStart</code></dt>
137 * <dd>The fraction of the path to trim from the start, in the range from 0 to 1.</dd>
138 * <dt><code>android:trimPathEnd</code></dt>
139 * <dd>The fraction of the path to trim from the end, in the range from 0 to 1.</dd>
140 * <dt><code>android:trimPathOffset</code></dt>
141 * <dd>Shift trim region (allows showed region to include the start and end), in the range
142 * from 0 to 1.</dd>
143 * <dt><code>android:strokeLineCap</code></dt>
144 * <dd>Sets the linecap for a stroked path: butt, round, square.</dd>
145 * <dt><code>android:strokeLineJoin</code></dt>
146 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel.</dd>
147 * <dt><code>android:strokeMiterLimit</code></dt>
148 * <dd>Sets the Miter limit for a stroked path.</dd>
149 * </dl></dd>
150 * </dl>
151 *
152 * <dl>
153 * <dt><code>&lt;clip-path></code></dt>
154 * <dd>Defines path to be the current clip. Note that the clip path only apply to
155 * the current group and its children.
156 * <dl>
157 * <dt><code>android:name</code></dt>
158 * <dd>Defines the name of the clip path.</dd>
159 * <dt><code>android:pathData</code></dt>
160 * <dd>Defines clip path using the same format as "d" attribute
161 * in the SVG's path data.</dd>
162 * </dl></dd>
163 * </dl>
164 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
165 * <pre>
166 * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
167 *     android:height=&quot;64dp&quot;
168 *     android:width=&quot;64dp&quot;
169 *     android:viewportHeight=&quot;600&quot;
170 *     android:viewportWidth=&quot;600&quot; &gt;
171 *     &lt;group
172 *         android:name=&quot;rotationGroup&quot;
173 *         android:pivotX=&quot;300.0&quot;
174 *         android:pivotY=&quot;300.0&quot;
175 *         android:rotation=&quot;45.0&quot; &gt;
176 *         &lt;path
177 *             android:name=&quot;v&quot;
178 *             android:fillColor=&quot;#000000&quot;
179 *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
180 *     &lt;/group&gt;
181 * &lt;/vector&gt;
182 * </pre></li>
183 */
184
185public class VectorDrawable extends Drawable {
186    private static final String LOGTAG = VectorDrawable.class.getSimpleName();
187
188    private static final String SHAPE_CLIP_PATH = "clip-path";
189    private static final String SHAPE_GROUP = "group";
190    private static final String SHAPE_PATH = "path";
191    private static final String SHAPE_VECTOR = "vector";
192
193    private static final int LINECAP_BUTT = 0;
194    private static final int LINECAP_ROUND = 1;
195    private static final int LINECAP_SQUARE = 2;
196
197    private static final int LINEJOIN_MITER = 0;
198    private static final int LINEJOIN_ROUND = 1;
199    private static final int LINEJOIN_BEVEL = 2;
200
201    private static final boolean DBG_VECTOR_DRAWABLE = false;
202
203    private VectorDrawableState mVectorState;
204
205    private PorterDuffColorFilter mTintFilter;
206    private ColorFilter mColorFilter;
207
208    private boolean mMutated;
209
210    // AnimatedVectorDrawable needs to turn off the cache all the time, otherwise,
211    // caching the bitmap by default is allowed.
212    private boolean mAllowCaching = true;
213
214    public VectorDrawable() {
215        mVectorState = new VectorDrawableState();
216    }
217
218    private VectorDrawable(@NonNull VectorDrawableState state) {
219        mVectorState = state;
220        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
221    }
222
223    @Override
224    public Drawable mutate() {
225        if (!mMutated && super.mutate() == this) {
226            mVectorState = new VectorDrawableState(mVectorState);
227            mMutated = true;
228        }
229        return this;
230    }
231
232    /**
233     * @hide
234     */
235    public void clearMutated() {
236        super.clearMutated();
237        mMutated = false;
238    }
239
240    Object getTargetByName(String name) {
241        return mVectorState.mVPathRenderer.mVGTargetsMap.get(name);
242    }
243
244    @Override
245    public ConstantState getConstantState() {
246        mVectorState.mChangingConfigurations = getChangingConfigurations();
247        return mVectorState;
248    }
249
250    @Override
251    public void draw(Canvas canvas) {
252        final Rect bounds = getBounds();
253        if (bounds.width() <= 0 || bounds.height() <= 0) {
254            // Nothing to draw
255            return;
256        }
257
258        final int saveCount = canvas.save();
259        final boolean needMirroring = needMirroring();
260
261        canvas.translate(bounds.left, bounds.top);
262        if (needMirroring) {
263            canvas.translate(bounds.width(), 0);
264            canvas.scale(-1.0f, 1.0f);
265        }
266
267        // Color filters always override tint filters.
268        final ColorFilter colorFilter = mColorFilter == null ? mTintFilter : mColorFilter;
269
270        if (!mAllowCaching) {
271            // AnimatedVectorDrawable
272            if (!mVectorState.hasTranslucentRoot()) {
273                mVectorState.mVPathRenderer.draw(
274                        canvas, bounds.width(), bounds.height(), colorFilter);
275            } else {
276                mVectorState.createCachedBitmapIfNeeded(bounds);
277                mVectorState.updateCachedBitmap(bounds);
278                mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter);
279            }
280        } else {
281            // Static Vector Drawable case.
282            mVectorState.createCachedBitmapIfNeeded(bounds);
283            if (!mVectorState.canReuseCache()) {
284                mVectorState.updateCachedBitmap(bounds);
285                mVectorState.updateCacheStates();
286            }
287            mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter);
288        }
289
290        canvas.restoreToCount(saveCount);
291    }
292
293    @Override
294    public int getAlpha() {
295        return mVectorState.mVPathRenderer.getRootAlpha();
296    }
297
298    @Override
299    public void setAlpha(int alpha) {
300        if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) {
301            mVectorState.mVPathRenderer.setRootAlpha(alpha);
302            invalidateSelf();
303        }
304    }
305
306    @Override
307    public void setColorFilter(ColorFilter colorFilter) {
308        mColorFilter = colorFilter;
309        invalidateSelf();
310    }
311
312    @Override
313    public void setTintList(ColorStateList tint) {
314        final VectorDrawableState state = mVectorState;
315        if (state.mTint != tint) {
316            state.mTint = tint;
317            mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode);
318            invalidateSelf();
319        }
320    }
321
322    @Override
323    public void setTintMode(Mode tintMode) {
324        final VectorDrawableState state = mVectorState;
325        if (state.mTintMode != tintMode) {
326            state.mTintMode = tintMode;
327            mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode);
328            invalidateSelf();
329        }
330    }
331
332    @Override
333    public boolean isStateful() {
334        return super.isStateful() || (mVectorState != null && mVectorState.mTint != null
335                && mVectorState.mTint.isStateful());
336    }
337
338    @Override
339    protected boolean onStateChange(int[] stateSet) {
340        final VectorDrawableState state = mVectorState;
341        if (state.mTint != null && state.mTintMode != null) {
342            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
343            invalidateSelf();
344            return true;
345        }
346        return false;
347    }
348
349    @Override
350    public int getOpacity() {
351        return PixelFormat.TRANSLUCENT;
352    }
353
354    @Override
355    public int getIntrinsicWidth() {
356        return (int) mVectorState.mVPathRenderer.mBaseWidth;
357    }
358
359    @Override
360    public int getIntrinsicHeight() {
361        return (int) mVectorState.mVPathRenderer.mBaseHeight;
362    }
363
364    @Override
365    public boolean canApplyTheme() {
366        return (mVectorState != null && mVectorState.canApplyTheme()) || super.canApplyTheme();
367    }
368
369    @Override
370    public void applyTheme(Theme t) {
371        super.applyTheme(t);
372
373        final VectorDrawableState state = mVectorState;
374        if (state == null) {
375            return;
376        }
377
378        if (state.mThemeAttrs != null) {
379            final TypedArray a = t.resolveAttributes(
380                    state.mThemeAttrs, R.styleable.VectorDrawable);
381            try {
382                state.mCacheDirty = true;
383                updateStateFromTypedArray(a);
384            } catch (XmlPullParserException e) {
385                throw new RuntimeException(e);
386            } finally {
387                a.recycle();
388            }
389        }
390
391        // Apply theme to contained color state list.
392        if (state.mTint != null && state.mTint.canApplyTheme()) {
393            state.mTint = state.mTint.obtainForTheme(t);
394        }
395
396        final VPathRenderer path = state.mVPathRenderer;
397        if (path != null && path.canApplyTheme()) {
398            path.applyTheme(t);
399        }
400
401        // Update local state.
402        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
403    }
404
405    /**
406     * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension.
407     * This is used to calculate the path animation accuracy.
408     *
409     * @hide
410     */
411    public float getPixelSize() {
412        if (mVectorState == null || mVectorState.mVPathRenderer == null ||
413                mVectorState.mVPathRenderer.mBaseWidth == 0 ||
414                mVectorState.mVPathRenderer.mBaseHeight == 0 ||
415                mVectorState.mVPathRenderer.mViewportHeight == 0 ||
416                mVectorState.mVPathRenderer.mViewportWidth == 0) {
417            return 1; // fall back to 1:1 pixel mapping.
418        }
419        float intrinsicWidth = mVectorState.mVPathRenderer.mBaseWidth;
420        float intrinsicHeight = mVectorState.mVPathRenderer.mBaseHeight;
421        float viewportWidth = mVectorState.mVPathRenderer.mViewportWidth;
422        float viewportHeight = mVectorState.mVPathRenderer.mViewportHeight;
423        float scaleX = viewportWidth / intrinsicWidth;
424        float scaleY = viewportHeight / intrinsicHeight;
425        return Math.min(scaleX, scaleY);
426    }
427
428    /** @hide */
429    public static VectorDrawable create(Resources resources, int rid) {
430        try {
431            final XmlPullParser parser = resources.getXml(rid);
432            final AttributeSet attrs = Xml.asAttributeSet(parser);
433            int type;
434            while ((type=parser.next()) != XmlPullParser.START_TAG &&
435                    type != XmlPullParser.END_DOCUMENT) {
436                // Empty loop
437            }
438            if (type != XmlPullParser.START_TAG) {
439                throw new XmlPullParserException("No start tag found");
440            }
441
442            final VectorDrawable drawable = new VectorDrawable();
443            drawable.inflate(resources, parser, attrs);
444
445            return drawable;
446        } catch (XmlPullParserException e) {
447            Log.e(LOGTAG, "parser error", e);
448        } catch (IOException e) {
449            Log.e(LOGTAG, "parser error", e);
450        }
451        return null;
452    }
453
454    private static int applyAlpha(int color, float alpha) {
455        int alphaBytes = Color.alpha(color);
456        color &= 0x00FFFFFF;
457        color |= ((int) (alphaBytes * alpha)) << 24;
458        return color;
459    }
460
461    @Override
462    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
463            throws XmlPullParserException, IOException {
464        final VectorDrawableState state = mVectorState;
465        final VPathRenderer pathRenderer = new VPathRenderer();
466        state.mVPathRenderer = pathRenderer;
467
468        final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable);
469        updateStateFromTypedArray(a);
470        a.recycle();
471
472        state.mCacheDirty = true;
473        inflateInternal(res, parser, attrs, theme);
474
475        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
476    }
477
478    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
479        final VectorDrawableState state = mVectorState;
480        final VPathRenderer pathRenderer = state.mVPathRenderer;
481
482        // Account for any configuration changes.
483        state.mChangingConfigurations |= a.getChangingConfigurations();
484
485        // Extract the theme attributes, if any.
486        state.mThemeAttrs = a.extractThemeAttrs();
487
488        final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
489        if (tintMode != -1) {
490            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
491        }
492
493        final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
494        if (tint != null) {
495            state.mTint = tint;
496        }
497
498        state.mAutoMirrored = a.getBoolean(
499                R.styleable.VectorDrawable_autoMirrored, state.mAutoMirrored);
500
501        pathRenderer.mViewportWidth = a.getFloat(
502                R.styleable.VectorDrawable_viewportWidth, pathRenderer.mViewportWidth);
503        pathRenderer.mViewportHeight = a.getFloat(
504                R.styleable.VectorDrawable_viewportHeight, pathRenderer.mViewportHeight);
505
506        if (pathRenderer.mViewportWidth <= 0) {
507            throw new XmlPullParserException(a.getPositionDescription() +
508                    "<vector> tag requires viewportWidth > 0");
509        } else if (pathRenderer.mViewportHeight <= 0) {
510            throw new XmlPullParserException(a.getPositionDescription() +
511                    "<vector> tag requires viewportHeight > 0");
512        }
513
514        pathRenderer.mBaseWidth = a.getDimension(
515                R.styleable.VectorDrawable_width, pathRenderer.mBaseWidth);
516        pathRenderer.mBaseHeight = a.getDimension(
517                R.styleable.VectorDrawable_height, pathRenderer.mBaseHeight);
518
519        if (pathRenderer.mBaseWidth <= 0) {
520            throw new XmlPullParserException(a.getPositionDescription() +
521                    "<vector> tag requires width > 0");
522        } else if (pathRenderer.mBaseHeight <= 0) {
523            throw new XmlPullParserException(a.getPositionDescription() +
524                    "<vector> tag requires height > 0");
525        }
526
527        final float alphaInFloat = a.getFloat(R.styleable.VectorDrawable_alpha,
528                pathRenderer.getAlpha());
529        pathRenderer.setAlpha(alphaInFloat);
530
531        final String name = a.getString(R.styleable.VectorDrawable_name);
532        if (name != null) {
533            pathRenderer.mRootName = name;
534            pathRenderer.mVGTargetsMap.put(name, pathRenderer);
535        }
536    }
537
538    private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
539            Theme theme) throws XmlPullParserException, IOException {
540        final VectorDrawableState state = mVectorState;
541        final VPathRenderer pathRenderer = state.mVPathRenderer;
542        boolean noPathTag = true;
543
544        // Use a stack to help to build the group tree.
545        // The top of the stack is always the current group.
546        final Stack<VGroup> groupStack = new Stack<VGroup>();
547        groupStack.push(pathRenderer.mRootGroup);
548
549        int eventType = parser.getEventType();
550        while (eventType != XmlPullParser.END_DOCUMENT) {
551            if (eventType == XmlPullParser.START_TAG) {
552                final String tagName = parser.getName();
553                final VGroup currentGroup = groupStack.peek();
554
555                if (SHAPE_PATH.equals(tagName)) {
556                    final VFullPath path = new VFullPath();
557                    path.inflate(res, attrs, theme);
558                    currentGroup.mChildren.add(path);
559                    if (path.getPathName() != null) {
560                        pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
561                    }
562                    noPathTag = false;
563                    state.mChangingConfigurations |= path.mChangingConfigurations;
564                } else if (SHAPE_CLIP_PATH.equals(tagName)) {
565                    final VClipPath path = new VClipPath();
566                    path.inflate(res, attrs, theme);
567                    currentGroup.mChildren.add(path);
568                    if (path.getPathName() != null) {
569                        pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
570                    }
571                    state.mChangingConfigurations |= path.mChangingConfigurations;
572                } else if (SHAPE_GROUP.equals(tagName)) {
573                    VGroup newChildGroup = new VGroup();
574                    newChildGroup.inflate(res, attrs, theme);
575                    currentGroup.mChildren.add(newChildGroup);
576                    groupStack.push(newChildGroup);
577                    if (newChildGroup.getGroupName() != null) {
578                        pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(),
579                                newChildGroup);
580                    }
581                    state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
582                }
583            } else if (eventType == XmlPullParser.END_TAG) {
584                final String tagName = parser.getName();
585                if (SHAPE_GROUP.equals(tagName)) {
586                    groupStack.pop();
587                }
588            }
589            eventType = parser.next();
590        }
591
592        // Print the tree out for debug.
593        if (DBG_VECTOR_DRAWABLE) {
594            printGroupTree(pathRenderer.mRootGroup, 0);
595        }
596
597        if (noPathTag) {
598            final StringBuffer tag = new StringBuffer();
599
600            if (tag.length() > 0) {
601                tag.append(" or ");
602            }
603            tag.append(SHAPE_PATH);
604
605            throw new XmlPullParserException("no " + tag + " defined");
606        }
607    }
608
609    private void printGroupTree(VGroup currentGroup, int level) {
610        String indent = "";
611        for (int i = 0; i < level; i++) {
612            indent += "    ";
613        }
614        // Print the current node
615        Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName()
616                + " rotation is " + currentGroup.mRotate);
617        Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString());
618        // Then print all the children groups
619        for (int i = 0; i < currentGroup.mChildren.size(); i++) {
620            Object child = currentGroup.mChildren.get(i);
621            if (child instanceof VGroup) {
622                printGroupTree((VGroup) child, level + 1);
623            }
624        }
625    }
626
627    @Override
628    public int getChangingConfigurations() {
629        return super.getChangingConfigurations() | mVectorState.getChangingConfigurations();
630    }
631
632    void setAllowCaching(boolean allowCaching) {
633        mAllowCaching = allowCaching;
634    }
635
636    private boolean needMirroring() {
637        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
638    }
639
640    @Override
641    public void setAutoMirrored(boolean mirrored) {
642        if (mVectorState.mAutoMirrored != mirrored) {
643            mVectorState.mAutoMirrored = mirrored;
644            invalidateSelf();
645        }
646    }
647
648    @Override
649    public boolean isAutoMirrored() {
650        return mVectorState.mAutoMirrored;
651    }
652
653    private static class VectorDrawableState extends ConstantState {
654        int[] mThemeAttrs;
655        int mChangingConfigurations;
656        VPathRenderer mVPathRenderer;
657        ColorStateList mTint = null;
658        Mode mTintMode = DEFAULT_TINT_MODE;
659        boolean mAutoMirrored;
660
661        Bitmap mCachedBitmap;
662        int[] mCachedThemeAttrs;
663        ColorStateList mCachedTint;
664        Mode mCachedTintMode;
665        int mCachedRootAlpha;
666        boolean mCachedAutoMirrored;
667        boolean mCacheDirty;
668
669        /** Temporary paint object used to draw cached bitmaps. */
670        Paint mTempPaint;
671
672        // Deep copy for mutate() or implicitly mutate.
673        public VectorDrawableState(VectorDrawableState copy) {
674            if (copy != null) {
675                mThemeAttrs = copy.mThemeAttrs;
676                mChangingConfigurations = copy.mChangingConfigurations;
677                mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
678                if (copy.mVPathRenderer.mFillPaint != null) {
679                    mVPathRenderer.mFillPaint = new Paint(copy.mVPathRenderer.mFillPaint);
680                }
681                if (copy.mVPathRenderer.mStrokePaint != null) {
682                    mVPathRenderer.mStrokePaint = new Paint(copy.mVPathRenderer.mStrokePaint);
683                }
684                mTint = copy.mTint;
685                mTintMode = copy.mTintMode;
686                mAutoMirrored = copy.mAutoMirrored;
687            }
688        }
689
690        public void drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter) {
691            // The bitmap's size is the same as the bounds.
692            final Paint p = getPaint(filter);
693            canvas.drawBitmap(mCachedBitmap, 0, 0, p);
694        }
695
696        public boolean hasTranslucentRoot() {
697            return mVPathRenderer.getRootAlpha() < 255;
698        }
699
700        /**
701         * @return null when there is no need for alpha paint.
702         */
703        public Paint getPaint(ColorFilter filter) {
704            if (!hasTranslucentRoot() && filter == null) {
705                return null;
706            }
707
708            if (mTempPaint == null) {
709                mTempPaint = new Paint();
710                mTempPaint.setFilterBitmap(true);
711            }
712            mTempPaint.setAlpha(mVPathRenderer.getRootAlpha());
713            mTempPaint.setColorFilter(filter);
714            return mTempPaint;
715        }
716
717        public void updateCachedBitmap(Rect bounds) {
718            mCachedBitmap.eraseColor(Color.TRANSPARENT);
719            Canvas tmpCanvas = new Canvas(mCachedBitmap);
720            mVPathRenderer.draw(tmpCanvas, bounds.width(), bounds.height(), null);
721        }
722
723        public void createCachedBitmapIfNeeded(Rect bounds) {
724            if (mCachedBitmap == null || !canReuseBitmap(bounds.width(),
725                    bounds.height())) {
726                mCachedBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(),
727                        Bitmap.Config.ARGB_8888);
728                mCacheDirty = true;
729            }
730
731        }
732
733        public boolean canReuseBitmap(int width, int height) {
734            if (width == mCachedBitmap.getWidth()
735                    && height == mCachedBitmap.getHeight()) {
736                return true;
737            }
738            return false;
739        }
740
741        public boolean canReuseCache() {
742            if (!mCacheDirty
743                    && mCachedThemeAttrs == mThemeAttrs
744                    && mCachedTint == mTint
745                    && mCachedTintMode == mTintMode
746                    && mCachedAutoMirrored == mAutoMirrored
747                    && mCachedRootAlpha == mVPathRenderer.getRootAlpha()) {
748                return true;
749            }
750            return false;
751        }
752
753        public void updateCacheStates() {
754            // Use shallow copy here and shallow comparison in canReuseCache(),
755            // likely hit cache miss more, but practically not much difference.
756            mCachedThemeAttrs = mThemeAttrs;
757            mCachedTint = mTint;
758            mCachedTintMode = mTintMode;
759            mCachedRootAlpha = mVPathRenderer.getRootAlpha();
760            mCachedAutoMirrored = mAutoMirrored;
761            mCacheDirty = false;
762        }
763
764        @Override
765        public boolean canApplyTheme() {
766            return mThemeAttrs != null
767                    || (mVPathRenderer != null && mVPathRenderer.canApplyTheme())
768                    || (mTint != null && mTint.canApplyTheme())
769                    || super.canApplyTheme();
770        }
771
772        public VectorDrawableState() {
773            mVPathRenderer = new VPathRenderer();
774        }
775
776        @Override
777        public Drawable newDrawable() {
778            return new VectorDrawable(this);
779        }
780
781        @Override
782        public Drawable newDrawable(Resources res) {
783            return new VectorDrawable(this);
784        }
785
786        @Override
787        public int getChangingConfigurations() {
788            return mChangingConfigurations
789                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
790        }
791    }
792
793    private static class VPathRenderer {
794        /* Right now the internal data structure is organized as a tree.
795         * Each node can be a group node, or a path.
796         * A group node can have groups or paths as children, but a path node has
797         * no children.
798         * One example can be:
799         *                 Root Group
800         *                /    |     \
801         *           Group    Path    Group
802         *          /     \             |
803         *         Path   Path         Path
804         *
805         */
806        // Variables that only used temporarily inside the draw() call, so there
807        // is no need for deep copying.
808        private final Path mPath;
809        private final Path mRenderPath;
810        private final Matrix mFinalPathMatrix = new Matrix();
811
812        private Paint mStrokePaint;
813        private Paint mFillPaint;
814        private PathMeasure mPathMeasure;
815
816        /////////////////////////////////////////////////////
817        // Variables below need to be copied (deep copy if applicable) for mutation.
818        private int mChangingConfigurations;
819        private final VGroup mRootGroup;
820        float mBaseWidth = 0;
821        float mBaseHeight = 0;
822        float mViewportWidth = 0;
823        float mViewportHeight = 0;
824        int mRootAlpha = 0xFF;
825        String mRootName = null;
826
827        final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>();
828
829        public VPathRenderer() {
830            mRootGroup = new VGroup();
831            mPath = new Path();
832            mRenderPath = new Path();
833        }
834
835        public void setRootAlpha(int alpha) {
836            mRootAlpha = alpha;
837        }
838
839        public int getRootAlpha() {
840            return mRootAlpha;
841        }
842
843        // setAlpha() and getAlpha() are used mostly for animation purpose, since
844        // Animator like to use alpha from 0 to 1.
845        public void setAlpha(float alpha) {
846            setRootAlpha((int) (alpha * 255));
847        }
848
849        @SuppressWarnings("unused")
850        public float getAlpha() {
851            return getRootAlpha() / 255.0f;
852        }
853
854        public VPathRenderer(VPathRenderer copy) {
855            mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
856            mPath = new Path(copy.mPath);
857            mRenderPath = new Path(copy.mRenderPath);
858            mBaseWidth = copy.mBaseWidth;
859            mBaseHeight = copy.mBaseHeight;
860            mViewportWidth = copy.mViewportWidth;
861            mViewportHeight = copy.mViewportHeight;
862            mChangingConfigurations = copy.mChangingConfigurations;
863            mRootAlpha = copy.mRootAlpha;
864            mRootName = copy.mRootName;
865            if (copy.mRootName != null) {
866                mVGTargetsMap.put(copy.mRootName, this);
867            }
868        }
869
870        public boolean canApplyTheme() {
871            // If one of the paths can apply theme, then return true;
872            return recursiveCanApplyTheme(mRootGroup);
873        }
874
875        private boolean recursiveCanApplyTheme(VGroup currentGroup) {
876            // We can do a tree traverse here, if there is one path return true,
877            // then we return true for the whole tree.
878            final ArrayList<Object> children = currentGroup.mChildren;
879
880            for (int i = 0; i < children.size(); i++) {
881                Object child = children.get(i);
882                if (child instanceof VGroup) {
883                    VGroup childGroup = (VGroup) child;
884                    if (childGroup.canApplyTheme()
885                            || recursiveCanApplyTheme(childGroup)) {
886                        return true;
887                    }
888                } else if (child instanceof VPath) {
889                    VPath childPath = (VPath) child;
890                    if (childPath.canApplyTheme()) {
891                        return true;
892                    }
893                }
894            }
895            return false;
896        }
897
898        public void applyTheme(Theme t) {
899            // Apply theme to every path of the tree.
900            recursiveApplyTheme(mRootGroup, t);
901        }
902
903        private void recursiveApplyTheme(VGroup currentGroup, Theme t) {
904            // We can do a tree traverse here, apply theme to all paths which
905            // can apply theme.
906            final ArrayList<Object> children = currentGroup.mChildren;
907            for (int i = 0; i < children.size(); i++) {
908                Object child = children.get(i);
909                if (child instanceof VGroup) {
910                    VGroup childGroup = (VGroup) child;
911                    if (childGroup.canApplyTheme()) {
912                        childGroup.applyTheme(t);
913                    }
914                    recursiveApplyTheme(childGroup, t);
915                } else if (child instanceof VPath) {
916                    VPath childPath = (VPath) child;
917                    if (childPath.canApplyTheme()) {
918                        childPath.applyTheme(t);
919                    }
920                }
921            }
922        }
923
924        private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
925                Canvas canvas, int w, int h, ColorFilter filter) {
926            // Calculate current group's matrix by preConcat the parent's and
927            // and the current one on the top of the stack.
928            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
929            // Mi the local matrix at level i of the group tree.
930            currentGroup.mStackedMatrix.set(currentMatrix);
931            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
932
933            // Save the current clip information, which is local to this group.
934            canvas.save();
935            // Draw the group tree in the same order as the XML file.
936            for (int i = 0; i < currentGroup.mChildren.size(); i++) {
937                Object child = currentGroup.mChildren.get(i);
938                if (child instanceof VGroup) {
939                    VGroup childGroup = (VGroup) child;
940                    drawGroupTree(childGroup, currentGroup.mStackedMatrix,
941                            canvas, w, h, filter);
942                } else if (child instanceof VPath) {
943                    VPath childPath = (VPath) child;
944                    drawPath(currentGroup, childPath, canvas, w, h, filter);
945                }
946            }
947            canvas.restore();
948        }
949
950        public void draw(Canvas canvas, int w, int h, ColorFilter filter) {
951            // Travese the tree in pre-order to draw.
952            drawGroupTree(mRootGroup, Matrix.IDENTITY_MATRIX, canvas, w, h, filter);
953        }
954
955        private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h,
956                ColorFilter filter) {
957            final float scaleX = w / mViewportWidth;
958            final float scaleY = h / mViewportHeight;
959            final float minScale = Math.min(scaleX, scaleY);
960            final Matrix groupStackedMatrix = vGroup.mStackedMatrix;
961
962            mFinalPathMatrix.set(groupStackedMatrix);
963            mFinalPathMatrix.postScale(scaleX, scaleY);
964
965            final float matrixScale = getMatrixScale(groupStackedMatrix);
966            if (matrixScale == 0) {
967                // When either x or y is scaled to 0, we don't need to draw anything.
968                return;
969            }
970            vPath.toPath(mPath);
971            final Path path = mPath;
972
973            mRenderPath.reset();
974
975            if (vPath.isClipPath()) {
976                mRenderPath.addPath(path, mFinalPathMatrix);
977                canvas.clipPath(mRenderPath);
978            } else {
979                VFullPath fullPath = (VFullPath) vPath;
980                if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
981                    float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
982                    float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;
983
984                    if (mPathMeasure == null) {
985                        mPathMeasure = new PathMeasure();
986                    }
987                    mPathMeasure.setPath(mPath, false);
988
989                    float len = mPathMeasure.getLength();
990                    start = start * len;
991                    end = end * len;
992                    path.reset();
993                    if (start > end) {
994                        mPathMeasure.getSegment(start, len, path, true);
995                        mPathMeasure.getSegment(0f, end, path, true);
996                    } else {
997                        mPathMeasure.getSegment(start, end, path, true);
998                    }
999                    path.rLineTo(0, 0); // fix bug in measure
1000                }
1001                mRenderPath.addPath(path, mFinalPathMatrix);
1002
1003                if (fullPath.mFillColor != Color.TRANSPARENT) {
1004                    if (mFillPaint == null) {
1005                        mFillPaint = new Paint();
1006                        mFillPaint.setStyle(Paint.Style.FILL);
1007                        mFillPaint.setAntiAlias(true);
1008                    }
1009
1010                    final Paint fillPaint = mFillPaint;
1011                    fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha));
1012                    fillPaint.setColorFilter(filter);
1013                    canvas.drawPath(mRenderPath, fillPaint);
1014                }
1015
1016                if (fullPath.mStrokeColor != Color.TRANSPARENT) {
1017                    if (mStrokePaint == null) {
1018                        mStrokePaint = new Paint();
1019                        mStrokePaint.setStyle(Paint.Style.STROKE);
1020                        mStrokePaint.setAntiAlias(true);
1021                    }
1022
1023                    final Paint strokePaint = mStrokePaint;
1024                    if (fullPath.mStrokeLineJoin != null) {
1025                        strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
1026                    }
1027
1028                    if (fullPath.mStrokeLineCap != null) {
1029                        strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
1030                    }
1031
1032                    strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
1033                    strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha));
1034                    strokePaint.setColorFilter(filter);
1035                    final float finalStrokeScale = minScale * matrixScale;
1036                    strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
1037                    canvas.drawPath(mRenderPath, strokePaint);
1038                }
1039            }
1040        }
1041
1042        private float getMatrixScale(Matrix groupStackedMatrix) {
1043            // Given unit vectors A = (0, 1) and B = (1, 0).
1044            // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
1045            // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
1046            // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
1047            // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
1048            //
1049            // For non-skew case, which is most of the cases, matrix scale is computing exactly the
1050            // scale on x and y axis, and take the minimal of these two.
1051            // For skew case, an unit square will mapped to a parallelogram. And this function will
1052            // return the minimal height of the 2 bases.
1053            float[] unitVectors = new float[] {0, 1, 1, 0};
1054            groupStackedMatrix.mapVectors(unitVectors);
1055            float scaleX = MathUtils.mag(unitVectors[0], unitVectors[1]);
1056            float scaleY = MathUtils.mag(unitVectors[2], unitVectors[3]);
1057            float crossProduct = MathUtils.cross(unitVectors[0], unitVectors[1],
1058                    unitVectors[2], unitVectors[3]);
1059            float maxScale = MathUtils.max(scaleX, scaleY);
1060
1061            float matrixScale = 0;
1062            if (maxScale > 0) {
1063                matrixScale = MathUtils.abs(crossProduct) / maxScale;
1064            }
1065            if (DBG_VECTOR_DRAWABLE) {
1066                Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
1067            }
1068            return matrixScale;
1069        }
1070    }
1071
1072    private static class VGroup {
1073        // mStackedMatrix is only used temporarily when drawing, it combines all
1074        // the parents' local matrices with the current one.
1075        private final Matrix mStackedMatrix = new Matrix();
1076
1077        /////////////////////////////////////////////////////
1078        // Variables below need to be copied (deep copy if applicable) for mutation.
1079        final ArrayList<Object> mChildren = new ArrayList<Object>();
1080
1081        private float mRotate = 0;
1082        private float mPivotX = 0;
1083        private float mPivotY = 0;
1084        private float mScaleX = 1;
1085        private float mScaleY = 1;
1086        private float mTranslateX = 0;
1087        private float mTranslateY = 0;
1088
1089        // mLocalMatrix is updated based on the update of transformation information,
1090        // either parsed from the XML or by animation.
1091        private final Matrix mLocalMatrix = new Matrix();
1092        private int mChangingConfigurations;
1093        private int[] mThemeAttrs;
1094        private String mGroupName = null;
1095
1096        public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
1097            mRotate = copy.mRotate;
1098            mPivotX = copy.mPivotX;
1099            mPivotY = copy.mPivotY;
1100            mScaleX = copy.mScaleX;
1101            mScaleY = copy.mScaleY;
1102            mTranslateX = copy.mTranslateX;
1103            mTranslateY = copy.mTranslateY;
1104            mThemeAttrs = copy.mThemeAttrs;
1105            mGroupName = copy.mGroupName;
1106            mChangingConfigurations = copy.mChangingConfigurations;
1107            if (mGroupName != null) {
1108                targetsMap.put(mGroupName, this);
1109            }
1110
1111            mLocalMatrix.set(copy.mLocalMatrix);
1112
1113            final ArrayList<Object> children = copy.mChildren;
1114            for (int i = 0; i < children.size(); i++) {
1115                Object copyChild = children.get(i);
1116                if (copyChild instanceof VGroup) {
1117                    VGroup copyGroup = (VGroup) copyChild;
1118                    mChildren.add(new VGroup(copyGroup, targetsMap));
1119                } else {
1120                    VPath newPath = null;
1121                    if (copyChild instanceof VFullPath) {
1122                        newPath = new VFullPath((VFullPath) copyChild);
1123                    } else if (copyChild instanceof VClipPath) {
1124                        newPath = new VClipPath((VClipPath) copyChild);
1125                    } else {
1126                        throw new IllegalStateException("Unknown object in the tree!");
1127                    }
1128                    mChildren.add(newPath);
1129                    if (newPath.mPathName != null) {
1130                        targetsMap.put(newPath.mPathName, newPath);
1131                    }
1132                }
1133            }
1134        }
1135
1136        public VGroup() {
1137        }
1138
1139        public String getGroupName() {
1140            return mGroupName;
1141        }
1142
1143        public Matrix getLocalMatrix() {
1144            return mLocalMatrix;
1145        }
1146
1147        public void inflate(Resources res, AttributeSet attrs, Theme theme) {
1148            final TypedArray a = obtainAttributes(res, theme, attrs,
1149                    R.styleable.VectorDrawableGroup);
1150            updateStateFromTypedArray(a);
1151            a.recycle();
1152        }
1153
1154        private void updateStateFromTypedArray(TypedArray a) {
1155            // Account for any configuration changes.
1156            mChangingConfigurations |= a.getChangingConfigurations();
1157
1158            // Extract the theme attributes, if any.
1159            mThemeAttrs = a.extractThemeAttrs();
1160
1161            mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate);
1162            mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX);
1163            mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY);
1164            mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX);
1165            mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY);
1166            mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX);
1167            mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
1168
1169            final String groupName = a.getString(R.styleable.VectorDrawableGroup_name);
1170            if (groupName != null) {
1171                mGroupName = groupName;
1172            }
1173
1174            updateLocalMatrix();
1175        }
1176
1177        public boolean canApplyTheme() {
1178            return mThemeAttrs != null;
1179        }
1180
1181        public void applyTheme(Theme t) {
1182            if (mThemeAttrs == null) {
1183                return;
1184            }
1185
1186            final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawableGroup);
1187            updateStateFromTypedArray(a);
1188            a.recycle();
1189        }
1190
1191        private void updateLocalMatrix() {
1192            // The order we apply is the same as the
1193            // RenderNode.cpp::applyViewPropertyTransforms().
1194            mLocalMatrix.reset();
1195            mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
1196            mLocalMatrix.postScale(mScaleX, mScaleY);
1197            mLocalMatrix.postRotate(mRotate, 0, 0);
1198            mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
1199        }
1200
1201        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1202        @SuppressWarnings("unused")
1203        public float getRotation() {
1204            return mRotate;
1205        }
1206
1207        @SuppressWarnings("unused")
1208        public void setRotation(float rotation) {
1209            if (rotation != mRotate) {
1210                mRotate = rotation;
1211                updateLocalMatrix();
1212            }
1213        }
1214
1215        @SuppressWarnings("unused")
1216        public float getPivotX() {
1217            return mPivotX;
1218        }
1219
1220        @SuppressWarnings("unused")
1221        public void setPivotX(float pivotX) {
1222            if (pivotX != mPivotX) {
1223                mPivotX = pivotX;
1224                updateLocalMatrix();
1225            }
1226        }
1227
1228        @SuppressWarnings("unused")
1229        public float getPivotY() {
1230            return mPivotY;
1231        }
1232
1233        @SuppressWarnings("unused")
1234        public void setPivotY(float pivotY) {
1235            if (pivotY != mPivotY) {
1236                mPivotY = pivotY;
1237                updateLocalMatrix();
1238            }
1239        }
1240
1241        @SuppressWarnings("unused")
1242        public float getScaleX() {
1243            return mScaleX;
1244        }
1245
1246        @SuppressWarnings("unused")
1247        public void setScaleX(float scaleX) {
1248            if (scaleX != mScaleX) {
1249                mScaleX = scaleX;
1250                updateLocalMatrix();
1251            }
1252        }
1253
1254        @SuppressWarnings("unused")
1255        public float getScaleY() {
1256            return mScaleY;
1257        }
1258
1259        @SuppressWarnings("unused")
1260        public void setScaleY(float scaleY) {
1261            if (scaleY != mScaleY) {
1262                mScaleY = scaleY;
1263                updateLocalMatrix();
1264            }
1265        }
1266
1267        @SuppressWarnings("unused")
1268        public float getTranslateX() {
1269            return mTranslateX;
1270        }
1271
1272        @SuppressWarnings("unused")
1273        public void setTranslateX(float translateX) {
1274            if (translateX != mTranslateX) {
1275                mTranslateX = translateX;
1276                updateLocalMatrix();
1277            }
1278        }
1279
1280        @SuppressWarnings("unused")
1281        public float getTranslateY() {
1282            return mTranslateY;
1283        }
1284
1285        @SuppressWarnings("unused")
1286        public void setTranslateY(float translateY) {
1287            if (translateY != mTranslateY) {
1288                mTranslateY = translateY;
1289                updateLocalMatrix();
1290            }
1291        }
1292    }
1293
1294    /**
1295     * Common Path information for clip path and normal path.
1296     */
1297    private static class VPath {
1298        protected PathParser.PathDataNode[] mNodes = null;
1299        String mPathName;
1300        int mChangingConfigurations;
1301
1302        public VPath() {
1303            // Empty constructor.
1304        }
1305
1306        public VPath(VPath copy) {
1307            mPathName = copy.mPathName;
1308            mChangingConfigurations = copy.mChangingConfigurations;
1309            mNodes = PathParser.deepCopyNodes(copy.mNodes);
1310        }
1311
1312        public void toPath(Path path) {
1313            path.reset();
1314            if (mNodes != null) {
1315                PathParser.PathDataNode.nodesToPath(mNodes, path);
1316            }
1317        }
1318
1319        public String getPathName() {
1320            return mPathName;
1321        }
1322
1323        public boolean canApplyTheme() {
1324            return false;
1325        }
1326
1327        public void applyTheme(Theme t) {
1328        }
1329
1330        public boolean isClipPath() {
1331            return false;
1332        }
1333
1334        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1335        @SuppressWarnings("unused")
1336        public PathParser.PathDataNode[] getPathData() {
1337            return mNodes;
1338        }
1339
1340        @SuppressWarnings("unused")
1341        public void setPathData(PathParser.PathDataNode[] nodes) {
1342            if (!PathParser.canMorph(mNodes, nodes)) {
1343                // This should not happen in the middle of animation.
1344                mNodes = PathParser.deepCopyNodes(nodes);
1345            } else {
1346                PathParser.updateNodes(mNodes, nodes);
1347            }
1348        }
1349    }
1350
1351    /**
1352     * Clip path, which only has name and pathData.
1353     */
1354    private static class VClipPath extends VPath {
1355        public VClipPath() {
1356            // Empty constructor.
1357        }
1358
1359        public VClipPath(VClipPath copy) {
1360            super(copy);
1361        }
1362
1363        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1364            final TypedArray a = obtainAttributes(r, theme, attrs,
1365                    R.styleable.VectorDrawableClipPath);
1366            updateStateFromTypedArray(a);
1367            a.recycle();
1368        }
1369
1370        private void updateStateFromTypedArray(TypedArray a) {
1371            // Account for any configuration changes.
1372            mChangingConfigurations |= a.getChangingConfigurations();
1373
1374            final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name);
1375            if (pathName != null) {
1376                mPathName = pathName;
1377            }
1378
1379            final String pathData = a.getString(R.styleable.VectorDrawableClipPath_pathData);
1380            if (pathData != null) {
1381                mNodes = PathParser.createNodesFromPathData(pathData);
1382            }
1383        }
1384
1385        @Override
1386        public boolean isClipPath() {
1387            return true;
1388        }
1389    }
1390
1391    /**
1392     * Normal path, which contains all the fill / paint information.
1393     */
1394    private static class VFullPath extends VPath {
1395        /////////////////////////////////////////////////////
1396        // Variables below need to be copied (deep copy if applicable) for mutation.
1397        private int[] mThemeAttrs;
1398
1399        int mStrokeColor = Color.TRANSPARENT;
1400        float mStrokeWidth = 0;
1401
1402        int mFillColor = Color.TRANSPARENT;
1403        float mStrokeAlpha = 1.0f;
1404        int mFillRule;
1405        float mFillAlpha = 1.0f;
1406        float mTrimPathStart = 0;
1407        float mTrimPathEnd = 1;
1408        float mTrimPathOffset = 0;
1409
1410        Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
1411        Paint.Join mStrokeLineJoin = Paint.Join.MITER;
1412        float mStrokeMiterlimit = 4;
1413
1414        public VFullPath() {
1415            // Empty constructor.
1416        }
1417
1418        public VFullPath(VFullPath copy) {
1419            super(copy);
1420            mThemeAttrs = copy.mThemeAttrs;
1421
1422            mStrokeColor = copy.mStrokeColor;
1423            mStrokeWidth = copy.mStrokeWidth;
1424            mStrokeAlpha = copy.mStrokeAlpha;
1425            mFillColor = copy.mFillColor;
1426            mFillRule = copy.mFillRule;
1427            mFillAlpha = copy.mFillAlpha;
1428            mTrimPathStart = copy.mTrimPathStart;
1429            mTrimPathEnd = copy.mTrimPathEnd;
1430            mTrimPathOffset = copy.mTrimPathOffset;
1431
1432            mStrokeLineCap = copy.mStrokeLineCap;
1433            mStrokeLineJoin = copy.mStrokeLineJoin;
1434            mStrokeMiterlimit = copy.mStrokeMiterlimit;
1435        }
1436
1437        private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
1438            switch (id) {
1439                case LINECAP_BUTT:
1440                    return Paint.Cap.BUTT;
1441                case LINECAP_ROUND:
1442                    return Paint.Cap.ROUND;
1443                case LINECAP_SQUARE:
1444                    return Paint.Cap.SQUARE;
1445                default:
1446                    return defValue;
1447            }
1448        }
1449
1450        private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) {
1451            switch (id) {
1452                case LINEJOIN_MITER:
1453                    return Paint.Join.MITER;
1454                case LINEJOIN_ROUND:
1455                    return Paint.Join.ROUND;
1456                case LINEJOIN_BEVEL:
1457                    return Paint.Join.BEVEL;
1458                default:
1459                    return defValue;
1460            }
1461        }
1462
1463        @Override
1464        public boolean canApplyTheme() {
1465            return mThemeAttrs != null;
1466        }
1467
1468        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1469            final TypedArray a = obtainAttributes(r, theme, attrs,
1470                    R.styleable.VectorDrawablePath);
1471            updateStateFromTypedArray(a);
1472            a.recycle();
1473        }
1474
1475        private void updateStateFromTypedArray(TypedArray a) {
1476            // Account for any configuration changes.
1477            mChangingConfigurations |= a.getChangingConfigurations();
1478
1479            // Extract the theme attributes, if any.
1480            mThemeAttrs = a.extractThemeAttrs();
1481
1482            final String pathName = a.getString(R.styleable.VectorDrawablePath_name);
1483            if (pathName != null) {
1484                mPathName = pathName;
1485            }
1486
1487            final String pathData = a.getString(R.styleable.VectorDrawablePath_pathData);
1488            if (pathData != null) {
1489                mNodes = PathParser.createNodesFromPathData(pathData);
1490            }
1491
1492            mFillColor = a.getColor(R.styleable.VectorDrawablePath_fillColor,
1493                    mFillColor);
1494            mFillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha,
1495                    mFillAlpha);
1496            mStrokeLineCap = getStrokeLineCap(a.getInt(
1497                    R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
1498            mStrokeLineJoin = getStrokeLineJoin(a.getInt(
1499                    R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
1500            mStrokeMiterlimit = a.getFloat(
1501                    R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
1502            mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_strokeColor,
1503                    mStrokeColor);
1504            mStrokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha,
1505                    mStrokeAlpha);
1506            mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
1507                    mStrokeWidth);
1508            mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
1509                    mTrimPathEnd);
1510            mTrimPathOffset = a.getFloat(
1511                    R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
1512            mTrimPathStart = a.getFloat(
1513                    R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
1514        }
1515
1516        @Override
1517        public void applyTheme(Theme t) {
1518            if (mThemeAttrs == null) {
1519                return;
1520            }
1521
1522            final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath);
1523            updateStateFromTypedArray(a);
1524            a.recycle();
1525        }
1526
1527        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1528        @SuppressWarnings("unused")
1529        int getStrokeColor() {
1530            return mStrokeColor;
1531        }
1532
1533        @SuppressWarnings("unused")
1534        void setStrokeColor(int strokeColor) {
1535            mStrokeColor = strokeColor;
1536        }
1537
1538        @SuppressWarnings("unused")
1539        float getStrokeWidth() {
1540            return mStrokeWidth;
1541        }
1542
1543        @SuppressWarnings("unused")
1544        void setStrokeWidth(float strokeWidth) {
1545            mStrokeWidth = strokeWidth;
1546        }
1547
1548        @SuppressWarnings("unused")
1549        float getStrokeAlpha() {
1550            return mStrokeAlpha;
1551        }
1552
1553        @SuppressWarnings("unused")
1554        void setStrokeAlpha(float strokeAlpha) {
1555            mStrokeAlpha = strokeAlpha;
1556        }
1557
1558        @SuppressWarnings("unused")
1559        int getFillColor() {
1560            return mFillColor;
1561        }
1562
1563        @SuppressWarnings("unused")
1564        void setFillColor(int fillColor) {
1565            mFillColor = fillColor;
1566        }
1567
1568        @SuppressWarnings("unused")
1569        float getFillAlpha() {
1570            return mFillAlpha;
1571        }
1572
1573        @SuppressWarnings("unused")
1574        void setFillAlpha(float fillAlpha) {
1575            mFillAlpha = fillAlpha;
1576        }
1577
1578        @SuppressWarnings("unused")
1579        float getTrimPathStart() {
1580            return mTrimPathStart;
1581        }
1582
1583        @SuppressWarnings("unused")
1584        void setTrimPathStart(float trimPathStart) {
1585            mTrimPathStart = trimPathStart;
1586        }
1587
1588        @SuppressWarnings("unused")
1589        float getTrimPathEnd() {
1590            return mTrimPathEnd;
1591        }
1592
1593        @SuppressWarnings("unused")
1594        void setTrimPathEnd(float trimPathEnd) {
1595            mTrimPathEnd = trimPathEnd;
1596        }
1597
1598        @SuppressWarnings("unused")
1599        float getTrimPathOffset() {
1600            return mTrimPathOffset;
1601        }
1602
1603        @SuppressWarnings("unused")
1604        void setTrimPathOffset(float trimPathOffset) {
1605            mTrimPathOffset = trimPathOffset;
1606        }
1607    }
1608}
1609