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