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