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