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