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