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