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