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