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