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