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><vector></code> element. 54ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * <p/> 55ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * The vector drawable has the following elements: 56ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * <p/> 57ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * <dt><code><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><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><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><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 * <vector xmlns:android="http://schemas.android.com/apk/res/android" 159 * android:height="64dp" 160 * android:width="64dp" 161 * android:viewportHeight="600" 162 * android:viewportWidth="600" > 163 * <group 164 * android:name="rotationGroup" 165 * android:pivotX="300.0" 166 * android:pivotY="300.0" 167 * android:rotation="45.0" > 168 * <path 169 * android:name="v" 170 * android:fillColor="#000000" 171 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 172 * </group> 173 * </vector> 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