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