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