[go: nahoru, domu]

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