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