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