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