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