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