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