VectorDrawable.java revision 5004032ebc2aee97c5884b7f91cc33d2f98ae8b5
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.Bitmap; 22import android.graphics.Canvas; 23import android.graphics.Color; 24import android.graphics.ColorFilter; 25import android.graphics.Matrix; 26import android.graphics.Paint; 27import android.graphics.Path; 28import android.graphics.PathMeasure; 29import android.graphics.PixelFormat; 30import android.graphics.PorterDuffColorFilter; 31import android.graphics.Rect; 32import android.graphics.Region; 33import android.graphics.PorterDuff.Mode; 34import android.util.ArrayMap; 35import android.util.AttributeSet; 36import android.util.LayoutDirection; 37import android.util.Log; 38import android.util.PathParser; 39import android.util.Xml; 40 41import com.android.internal.R; 42 43import org.xmlpull.v1.XmlPullParser; 44import org.xmlpull.v1.XmlPullParserException; 45 46import java.io.IOException; 47import java.util.ArrayList; 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 * <dt><code><vector></code></dt> 57 * <dl> 58 * <dd>Used to defined a vector drawable 59 * <dl> 60 * <dt><code>android:width</code></dt> 61 * <dd>Used to defined the intrinsic width of the drawable. 62 * This support all the dimension units, normally specified with dp.</dd> 63 * <dt><code>android:height</code></dt> 64 * <dd>Used to defined the intrinsic height the drawable. 65 * This support all the dimension units, normally specified with dp.</dd> 66 * <dt><code>android:viewportWidth</code></dt> 67 * <dd>Used to defined the width of the viewport space. Viewport is basically 68 * the virtual canvas where the paths are drawn on.</dd> 69 * <dt><code>android:viewportHeight</code></dt> 70 * <dd>Used to defined the height of the viewport space. Viewport is basically 71 * the virtual canvas where the paths are drawn on.</dd> 72 * <dt><code>android:tint</code></dt> 73 * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd> 74 * <dt><code>android:tintMode</code></dt> 75 * <dd>The Porter-Duff blending mode for the tint color. The default value is src_in.</dd> 76 * <dt><code>android:autoMirrored</code></dt> 77 * <dd>Indicates if the drawable needs to be mirrored when its layout direction is 78 * RTL (right-to-left).</dd> 79 * </dl></dd> 80 * </dl> 81 * 82 * <dl> 83 * <dt><code><group></code></dt> 84 * <dd>Defines a group of paths or subgroups, plus transformation information. 85 * The transformations are defined in the same coordinates as the viewport. 86 * And the transformations are applied in the order of scale, rotate then translate. 87 * <dl> 88 * <dt><code>android:rotation</code></dt> 89 * <dd>The degrees of rotation of the group.</dd> 90 * <dt><code>android:pivotX</code></dt> 91 * <dd>The X coordinate of the pivot for the scale and rotation of the group. 92 * This is defined in the viewport space.</dd> 93 * <dt><code>android:pivotY</code></dt> 94 * <dd>The Y coordinate of the pivot for the scale and rotation of the group. 95 * This is defined in the viewport space.</dd> 96 * <dt><code>android:scaleX</code></dt> 97 * <dd>The amount of scale on the X Coordinate.</dd> 98 * <dt><code>android:scaleY</code></dt> 99 * <dd>The amount of scale on the Y coordinate.</dd> 100 * <dt><code>android:translateX</code></dt> 101 * <dd>The amount of translation on the X coordinate. 102 * This is defined in the viewport space.</dd> 103 * <dt><code>android:translateY</code></dt> 104 * <dd>The amount of translation on the Y coordinate. 105 * This is defined in the viewport space.</dd> 106 * </dl></dd> 107 * </dl> 108 * 109 * <dl> 110 * <dt><code><path></code></dt> 111 * <dd>Defines paths to be drawn. 112 * <dl> 113 * <dt><code>android:name</code></dt> 114 * <dd>Defines the name of the path.</dd> 115 * <dt><code>android:pathData</code></dt> 116 * <dd>Defines path string. This is using exactly same format as "d" attribute 117 * in the SVG's path data. This is defined in the viewport space.</dd> 118 * <dt><code>android:fillColor</code></dt> 119 * <dd>Defines the color to fill the path (none if not present).</dd> 120 * <dt><code>android:strokeColor</code></dt> 121 * <dd>Defines the color to draw the path outline (none if not present).</dd> 122 * <dt><code>android:strokeWidth</code></dt> 123 * <dd>The width a path stroke.</dd> 124 * <dt><code>android:strokeAlpha</code></dt> 125 * <dd>The opacity of a path stroke.</dd> 126 * <dt><code>android:fillAlpha</code></dt> 127 * <dd>The opacity to fill the path with.</dd> 128 * <dt><code>android:trimPathStart</code></dt> 129 * <dd>The fraction of the path to trim from the start, in the range from 0 to 1.</dd> 130 * <dt><code>android:trimPathEnd</code></dt> 131 * <dd>The fraction of the path to trim from the end, in the range from 0 to 1.</dd> 132 * <dt><code>android:trimPathOffset</code></dt> 133 * <dd>Shift trim region (allows showed region to include the start and end), in the range 134 * from 0 to 1.</dd> 135 * <dt><code>android:strokeLineCap</code></dt> 136 * <dd>Sets the linecap for a stroked path: butt, round, square.</dd> 137 * <dt><code>android:strokeLineJoin</code></dt> 138 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel.</dd> 139 * <dt><code>android:strokeMiterLimit</code></dt> 140 * <dd>Sets the Miter limit for a stroked path.</dd> 141 * </dl></dd> 142 * </dl> 143 * 144 * <dl> 145 * <dt><code><clip-path></code></dt> 146 * <dd>Defines path to be the current clip. 147 * <dl> 148 * <dt><code>android:name</code></dt> 149 * <dd>Defines the name of the clip path.</dd> 150 * <dt><code>android:pathData</code></dt> 151 * <dd>Defines clip path string. This is using exactly same format as "d" attribute 152 * in the SVG's path data.</dd> 153 * </dl></dd> 154 * </dl> 155 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file. 156 * <pre> 157 * <vector xmlns:android="http://schemas.android.com/apk/res/android" 158 * android:height="64dp" 159 * android:width="64dp" 160 * android:viewportHeight="600" 161 * android:viewportWidth="600" > 162 * <group 163 * android:name="rotationGroup" 164 * android:pivotX="300.0" 165 * android:pivotY="300.0" 166 * android:rotation="45.0" > 167 * <path 168 * android:name="v" 169 * android:fillColor="#000000" 170 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 171 * </group> 172 * </vector> 173 * </pre></li> 174 */ 175 176public class VectorDrawable extends Drawable { 177 private static final String LOGTAG = VectorDrawable.class.getSimpleName(); 178 179 private static final String SHAPE_CLIP_PATH = "clip-path"; 180 private static final String SHAPE_GROUP = "group"; 181 private static final String SHAPE_PATH = "path"; 182 private static final String SHAPE_VECTOR = "vector"; 183 184 private static final int LINECAP_BUTT = 0; 185 private static final int LINECAP_ROUND = 1; 186 private static final int LINECAP_SQUARE = 2; 187 188 private static final int LINEJOIN_MITER = 0; 189 private static final int LINEJOIN_ROUND = 1; 190 private static final int LINEJOIN_BEVEL = 2; 191 192 private static final boolean DBG_VECTOR_DRAWABLE = false; 193 194 private VectorDrawableState mVectorState; 195 196 private PorterDuffColorFilter mTintFilter; 197 private ColorFilter mColorFilter; 198 199 private boolean mMutated; 200 201 // AnimatedVectorDrawable needs to turn off the cache all the time, otherwise, 202 // caching the bitmap by default is allowed. 203 private boolean mAllowCaching = true; 204 205 public VectorDrawable() { 206 mVectorState = new VectorDrawableState(); 207 } 208 209 private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) { 210 if (theme != null && state.canApplyTheme()) { 211 // If we need to apply a theme, implicitly mutate. 212 mVectorState = new VectorDrawableState(state); 213 applyTheme(theme); 214 } else { 215 mVectorState = state; 216 } 217 218 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 219 } 220 221 @Override 222 public Drawable mutate() { 223 if (!mMutated && super.mutate() == this) { 224 mVectorState = new VectorDrawableState(mVectorState); 225 mMutated = true; 226 } 227 return this; 228 } 229 230 Object getTargetByName(String name) { 231 return mVectorState.mVPathRenderer.mVGTargetsMap.get(name); 232 } 233 234 @Override 235 public ConstantState getConstantState() { 236 mVectorState.mChangingConfigurations = getChangingConfigurations(); 237 return mVectorState; 238 } 239 240 @Override 241 public void draw(Canvas canvas) { 242 final Rect bounds = getBounds(); 243 if (bounds.width() == 0 || bounds.height() == 0) { 244 // too small to draw 245 return; 246 } 247 248 final int saveCount = canvas.save(); 249 final boolean needMirroring = needMirroring(); 250 251 canvas.translate(bounds.left, bounds.top); 252 if (needMirroring) { 253 canvas.translate(bounds.width(), 0); 254 canvas.scale(-1.0f, 1.0f); 255 } 256 257 // Color filters always override tint filters. 258 final ColorFilter colorFilter = mColorFilter == null ? mTintFilter : mColorFilter; 259 260 if (!mAllowCaching) { 261 // AnimatedVectorDrawable 262 if (!mVectorState.hasTranslucentRoot()) { 263 mVectorState.mVPathRenderer.draw( 264 canvas, bounds.width(), bounds.height(), colorFilter); 265 } else { 266 mVectorState.createCachedBitmapIfNeeded(bounds); 267 mVectorState.updateCachedBitmap(bounds); 268 mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter); 269 } 270 } else { 271 // Static Vector Drawable case. 272 mVectorState.createCachedBitmapIfNeeded(bounds); 273 if (!mVectorState.canReuseCache()) { 274 mVectorState.updateCachedBitmap(bounds); 275 mVectorState.updateCacheStates(); 276 } 277 mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter); 278 } 279 280 canvas.restoreToCount(saveCount); 281 } 282 283 @Override 284 public int getAlpha() { 285 return mVectorState.mVPathRenderer.getRootAlpha(); 286 } 287 288 @Override 289 public void setAlpha(int alpha) { 290 if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) { 291 mVectorState.mVPathRenderer.setRootAlpha(alpha); 292 invalidateSelf(); 293 } 294 } 295 296 @Override 297 public void setColorFilter(ColorFilter colorFilter) { 298 mColorFilter = colorFilter; 299 invalidateSelf(); 300 } 301 302 @Override 303 public void setTintList(ColorStateList tint) { 304 final VectorDrawableState state = mVectorState; 305 if (state.mTint != tint) { 306 state.mTint = tint; 307 mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode); 308 invalidateSelf(); 309 } 310 } 311 312 @Override 313 public void setTintMode(Mode tintMode) { 314 final VectorDrawableState state = mVectorState; 315 if (state.mTintMode != tintMode) { 316 state.mTintMode = tintMode; 317 mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode); 318 invalidateSelf(); 319 } 320 } 321 322 @Override 323 public boolean isStateful() { 324 return super.isStateful() || (mVectorState != null && mVectorState.mTint != null 325 && mVectorState.mTint.isStateful()); 326 } 327 328 @Override 329 protected boolean onStateChange(int[] stateSet) { 330 final VectorDrawableState state = mVectorState; 331 if (state.mTint != null && state.mTintMode != null) { 332 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 333 invalidateSelf(); 334 return true; 335 } 336 return false; 337 } 338 339 @Override 340 public int getOpacity() { 341 return PixelFormat.TRANSLUCENT; 342 } 343 344 @Override 345 public int getIntrinsicWidth() { 346 return (int) mVectorState.mVPathRenderer.mBaseWidth; 347 } 348 349 @Override 350 public int getIntrinsicHeight() { 351 return (int) mVectorState.mVPathRenderer.mBaseHeight; 352 } 353 354 @Override 355 public boolean canApplyTheme() { 356 return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme(); 357 } 358 359 @Override 360 public void applyTheme(Theme t) { 361 super.applyTheme(t); 362 363 final VectorDrawableState state = mVectorState; 364 if (state != null && state.mThemeAttrs != null) { 365 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.VectorDrawable); 366 try { 367 state.mCacheDirty = true; 368 updateStateFromTypedArray(a); 369 } catch (XmlPullParserException e) { 370 throw new RuntimeException(e); 371 } finally { 372 a.recycle(); 373 } 374 375 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 376 } 377 378 final VPathRenderer path = state.mVPathRenderer; 379 if (path != null && path.canApplyTheme()) { 380 path.applyTheme(t); 381 } 382 } 383 384 /** 385 * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension. 386 * This is used to calculate the path animation accuracy. 387 * 388 * @hide 389 */ 390 public float getPixelSize() { 391 if (mVectorState == null && mVectorState.mVPathRenderer == null || 392 mVectorState.mVPathRenderer.mBaseWidth == 0 || 393 mVectorState.mVPathRenderer.mBaseHeight == 0 || 394 mVectorState.mVPathRenderer.mViewportHeight == 0 || 395 mVectorState.mVPathRenderer.mViewportWidth == 0) { 396 return 1; // fall back to 1:1 pixel mapping. 397 } 398 float intrinsicWidth = mVectorState.mVPathRenderer.mBaseWidth; 399 float intrinsicHeight = mVectorState.mVPathRenderer.mBaseHeight; 400 float viewportWidth = mVectorState.mVPathRenderer.mViewportWidth; 401 float viewportHeight = mVectorState.mVPathRenderer.mViewportHeight; 402 float scaleX = viewportWidth / intrinsicWidth; 403 float scaleY = viewportHeight / intrinsicHeight; 404 return Math.min(scaleX, scaleY); 405 } 406 407 /** @hide */ 408 public static VectorDrawable create(Resources resources, int rid) { 409 try { 410 final XmlPullParser parser = resources.getXml(rid); 411 final AttributeSet attrs = Xml.asAttributeSet(parser); 412 int type; 413 while ((type=parser.next()) != XmlPullParser.START_TAG && 414 type != XmlPullParser.END_DOCUMENT) { 415 // Empty loop 416 } 417 if (type != XmlPullParser.START_TAG) { 418 throw new XmlPullParserException("No start tag found"); 419 } 420 421 final VectorDrawable drawable = new VectorDrawable(); 422 drawable.inflate(resources, parser, attrs); 423 424 return drawable; 425 } catch (XmlPullParserException e) { 426 Log.e(LOGTAG, "parser error", e); 427 } catch (IOException e) { 428 Log.e(LOGTAG, "parser error", e); 429 } 430 return null; 431 } 432 433 private static int applyAlpha(int color, float alpha) { 434 int alphaBytes = Color.alpha(color); 435 color &= 0x00FFFFFF; 436 color |= ((int) (alphaBytes * alpha)) << 24; 437 return color; 438 } 439 440 @Override 441 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 442 throws XmlPullParserException, IOException { 443 final VectorDrawableState state = mVectorState; 444 final VPathRenderer pathRenderer = new VPathRenderer(); 445 state.mVPathRenderer = pathRenderer; 446 447 final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable); 448 updateStateFromTypedArray(a); 449 a.recycle(); 450 451 state.mCacheDirty = true; 452 inflateInternal(res, parser, attrs, theme); 453 454 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 455 } 456 457 private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { 458 final VectorDrawableState state = mVectorState; 459 final VPathRenderer pathRenderer = state.mVPathRenderer; 460 461 // Account for any configuration changes. 462 state.mChangingConfigurations |= a.getChangingConfigurations(); 463 464 // Extract the theme attributes, if any. 465 state.mThemeAttrs = a.extractThemeAttrs(); 466 467 final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1); 468 if (tintMode != -1) { 469 state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); 470 } 471 472 final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint); 473 if (tint != null) { 474 state.mTint = tint; 475 } 476 477 state.mAutoMirrored = a.getBoolean( 478 R.styleable.VectorDrawable_autoMirrored, state.mAutoMirrored); 479 480 pathRenderer.mViewportWidth = a.getFloat( 481 R.styleable.VectorDrawable_viewportWidth, pathRenderer.mViewportWidth); 482 pathRenderer.mViewportHeight = a.getFloat( 483 R.styleable.VectorDrawable_viewportHeight, pathRenderer.mViewportHeight); 484 485 if (pathRenderer.mViewportWidth <= 0) { 486 throw new XmlPullParserException(a.getPositionDescription() + 487 "<vector> tag requires viewportWidth > 0"); 488 } else if (pathRenderer.mViewportHeight <= 0) { 489 throw new XmlPullParserException(a.getPositionDescription() + 490 "<vector> tag requires viewportHeight > 0"); 491 } 492 493 pathRenderer.mBaseWidth = a.getDimension( 494 R.styleable.VectorDrawable_width, pathRenderer.mBaseWidth); 495 pathRenderer.mBaseHeight = a.getDimension( 496 R.styleable.VectorDrawable_height, pathRenderer.mBaseHeight); 497 498 if (pathRenderer.mBaseWidth <= 0) { 499 throw new XmlPullParserException(a.getPositionDescription() + 500 "<vector> tag requires width > 0"); 501 } else if (pathRenderer.mBaseHeight <= 0) { 502 throw new XmlPullParserException(a.getPositionDescription() + 503 "<vector> tag requires height > 0"); 504 } 505 506 final float alphaInFloat = a.getFloat(R.styleable.VectorDrawable_alpha, 507 pathRenderer.getAlpha()); 508 pathRenderer.setAlpha(alphaInFloat); 509 510 final String name = a.getString(R.styleable.VectorDrawable_name); 511 if (name != null) { 512 pathRenderer.mRootName = name; 513 pathRenderer.mVGTargetsMap.put(name, pathRenderer); 514 } 515 } 516 517 private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, 518 Theme theme) throws XmlPullParserException, IOException { 519 final VectorDrawableState state = mVectorState; 520 final VPathRenderer pathRenderer = state.mVPathRenderer; 521 boolean noPathTag = true; 522 523 // Use a stack to help to build the group tree. 524 // The top of the stack is always the current group. 525 final Stack<VGroup> groupStack = new Stack<VGroup>(); 526 groupStack.push(pathRenderer.mRootGroup); 527 528 int eventType = parser.getEventType(); 529 while (eventType != XmlPullParser.END_DOCUMENT) { 530 if (eventType == XmlPullParser.START_TAG) { 531 final String tagName = parser.getName(); 532 final VGroup currentGroup = groupStack.peek(); 533 534 if (SHAPE_PATH.equals(tagName)) { 535 final VFullPath path = new VFullPath(); 536 path.inflate(res, attrs, theme); 537 currentGroup.mChildren.add(path); 538 if (path.getPathName() != null) { 539 pathRenderer.mVGTargetsMap.put(path.getPathName(), path); 540 } 541 noPathTag = false; 542 state.mChangingConfigurations |= path.mChangingConfigurations; 543 } else if (SHAPE_CLIP_PATH.equals(tagName)) { 544 final VClipPath path = new VClipPath(); 545 path.inflate(res, attrs, theme); 546 currentGroup.mChildren.add(path); 547 if (path.getPathName() != null) { 548 pathRenderer.mVGTargetsMap.put(path.getPathName(), path); 549 } 550 state.mChangingConfigurations |= path.mChangingConfigurations; 551 } else if (SHAPE_GROUP.equals(tagName)) { 552 VGroup newChildGroup = new VGroup(); 553 newChildGroup.inflate(res, attrs, theme); 554 currentGroup.mChildren.add(newChildGroup); 555 groupStack.push(newChildGroup); 556 if (newChildGroup.getGroupName() != null) { 557 pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(), 558 newChildGroup); 559 } 560 state.mChangingConfigurations |= newChildGroup.mChangingConfigurations; 561 } 562 } else if (eventType == XmlPullParser.END_TAG) { 563 final String tagName = parser.getName(); 564 if (SHAPE_GROUP.equals(tagName)) { 565 groupStack.pop(); 566 } 567 } 568 eventType = parser.next(); 569 } 570 571 // Print the tree out for debug. 572 if (DBG_VECTOR_DRAWABLE) { 573 printGroupTree(pathRenderer.mRootGroup, 0); 574 } 575 576 if (noPathTag) { 577 final StringBuffer tag = new StringBuffer(); 578 579 if (tag.length() > 0) { 580 tag.append(" or "); 581 } 582 tag.append(SHAPE_PATH); 583 584 throw new XmlPullParserException("no " + tag + " defined"); 585 } 586 } 587 588 private void printGroupTree(VGroup currentGroup, int level) { 589 String indent = ""; 590 for (int i = 0; i < level; i++) { 591 indent += " "; 592 } 593 // Print the current node 594 Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName() 595 + " rotation is " + currentGroup.mRotate); 596 Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString()); 597 // Then print all the children groups 598 for (int i = 0; i < currentGroup.mChildren.size(); i++) { 599 Object child = currentGroup.mChildren.get(i); 600 if (child instanceof VGroup) { 601 printGroupTree((VGroup) child, level + 1); 602 } 603 } 604 } 605 606 @Override 607 public int getChangingConfigurations() { 608 return super.getChangingConfigurations() | mVectorState.mChangingConfigurations; 609 } 610 611 void setAllowCaching(boolean allowCaching) { 612 mAllowCaching = allowCaching; 613 } 614 615 private boolean needMirroring() { 616 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 617 } 618 619 @Override 620 public void setAutoMirrored(boolean mirrored) { 621 if (mVectorState.mAutoMirrored != mirrored) { 622 mVectorState.mAutoMirrored = mirrored; 623 invalidateSelf(); 624 } 625 } 626 627 @Override 628 public boolean isAutoMirrored() { 629 return mVectorState.mAutoMirrored; 630 } 631 632 private static class VectorDrawableState extends ConstantState { 633 int[] mThemeAttrs; 634 int mChangingConfigurations; 635 VPathRenderer mVPathRenderer; 636 ColorStateList mTint = null; 637 Mode mTintMode = DEFAULT_TINT_MODE; 638 boolean mAutoMirrored; 639 640 Bitmap mCachedBitmap; 641 int[] mCachedThemeAttrs; 642 ColorStateList mCachedTint; 643 Mode mCachedTintMode; 644 int mCachedRootAlpha; 645 boolean mCachedAutoMirrored; 646 boolean mCacheDirty; 647 648 /** Temporary paint object used to draw cached bitmaps. */ 649 Paint mTempPaint; 650 651 // Deep copy for mutate() or implicitly mutate. 652 public VectorDrawableState(VectorDrawableState copy) { 653 if (copy != null) { 654 mThemeAttrs = copy.mThemeAttrs; 655 mChangingConfigurations = copy.mChangingConfigurations; 656 mVPathRenderer = new VPathRenderer(copy.mVPathRenderer); 657 if (copy.mVPathRenderer.mFillPaint != null) { 658 mVPathRenderer.mFillPaint = new Paint(copy.mVPathRenderer.mFillPaint); 659 } 660 if (copy.mVPathRenderer.mStrokePaint != null) { 661 mVPathRenderer.mStrokePaint = new Paint(copy.mVPathRenderer.mStrokePaint); 662 } 663 mTint = copy.mTint; 664 mTintMode = copy.mTintMode; 665 mAutoMirrored = copy.mAutoMirrored; 666 } 667 } 668 669 public void drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter) { 670 // The bitmap's size is the same as the bounds. 671 final Paint p = getPaint(filter); 672 canvas.drawBitmap(mCachedBitmap, 0, 0, p); 673 } 674 675 public boolean hasTranslucentRoot() { 676 return mVPathRenderer.getRootAlpha() < 255; 677 } 678 679 /** 680 * @return null when there is no need for alpha paint. 681 */ 682 public Paint getPaint(ColorFilter filter) { 683 if (!hasTranslucentRoot() && filter == null) { 684 return null; 685 } 686 687 if (mTempPaint == null) { 688 mTempPaint = new Paint(); 689 } 690 mTempPaint.setAlpha(mVPathRenderer.getRootAlpha()); 691 mTempPaint.setColorFilter(filter); 692 return mTempPaint; 693 } 694 695 public void updateCachedBitmap(Rect bounds) { 696 mCachedBitmap.eraseColor(Color.TRANSPARENT); 697 Canvas tmpCanvas = new Canvas(mCachedBitmap); 698 mVPathRenderer.draw(tmpCanvas, bounds.width(), bounds.height(), null); 699 } 700 701 public void createCachedBitmapIfNeeded(Rect bounds) { 702 if (mCachedBitmap == null || !canReuseBitmap(bounds.width(), 703 bounds.height())) { 704 mCachedBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), 705 Bitmap.Config.ARGB_8888); 706 mCacheDirty = true; 707 } 708 709 } 710 711 public boolean canReuseBitmap(int width, int height) { 712 if (width == mCachedBitmap.getWidth() 713 && height == mCachedBitmap.getHeight()) { 714 return true; 715 } 716 return false; 717 } 718 719 public boolean canReuseCache() { 720 if (!mCacheDirty 721 && mCachedThemeAttrs == mThemeAttrs 722 && mCachedTint == mTint 723 && mCachedTintMode == mTintMode 724 && mCachedAutoMirrored == mAutoMirrored 725 && mCachedRootAlpha == mVPathRenderer.getRootAlpha()) { 726 return true; 727 } 728 return false; 729 } 730 731 public void updateCacheStates() { 732 // Use shallow copy here and shallow comparison in canReuseCache(), 733 // likely hit cache miss more, but practically not much difference. 734 mCachedThemeAttrs = mThemeAttrs; 735 mCachedTint = mTint; 736 mCachedTintMode = mTintMode; 737 mCachedRootAlpha = mVPathRenderer.getRootAlpha(); 738 mCachedAutoMirrored = mAutoMirrored; 739 mCacheDirty = false; 740 } 741 742 @Override 743 public boolean canApplyTheme() { 744 return super.canApplyTheme() || mThemeAttrs != null 745 || (mVPathRenderer != null && mVPathRenderer.canApplyTheme()); 746 } 747 748 public VectorDrawableState() { 749 mVPathRenderer = new VPathRenderer(); 750 } 751 752 @Override 753 public Drawable newDrawable() { 754 return new VectorDrawable(this, null, null); 755 } 756 757 @Override 758 public Drawable newDrawable(Resources res) { 759 return new VectorDrawable(this, res, null); 760 } 761 762 @Override 763 public Drawable newDrawable(Resources res, Theme theme) { 764 return new VectorDrawable(this, res, theme); 765 } 766 767 @Override 768 public int getChangingConfigurations() { 769 return mChangingConfigurations; 770 } 771 } 772 773 private static class VPathRenderer { 774 /* Right now the internal data structure is organized as a tree. 775 * Each node can be a group node, or a path. 776 * A group node can have groups or paths as children, but a path node has 777 * no children. 778 * One example can be: 779 * Root Group 780 * / | \ 781 * Group Path Group 782 * / \ | 783 * Path Path Path 784 * 785 */ 786 // Variables that only used temporarily inside the draw() call, so there 787 // is no need for deep copying. 788 private final Path mPath; 789 private final Path mRenderPath; 790 private static final Matrix IDENTITY_MATRIX = new Matrix(); 791 private final Matrix mFinalPathMatrix = new Matrix(); 792 793 private Paint mStrokePaint; 794 private Paint mFillPaint; 795 private PathMeasure mPathMeasure; 796 797 ///////////////////////////////////////////////////// 798 // Variables below need to be copied (deep copy if applicable) for mutation. 799 private int mChangingConfigurations; 800 private final VGroup mRootGroup; 801 float mBaseWidth = 0; 802 float mBaseHeight = 0; 803 float mViewportWidth = 0; 804 float mViewportHeight = 0; 805 int mRootAlpha = 0xFF; 806 String mRootName = null; 807 808 final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>(); 809 810 public VPathRenderer() { 811 mRootGroup = new VGroup(); 812 mPath = new Path(); 813 mRenderPath = new Path(); 814 } 815 816 public void setRootAlpha(int alpha) { 817 mRootAlpha = alpha; 818 } 819 820 public int getRootAlpha() { 821 return mRootAlpha; 822 } 823 824 // setAlpha() and getAlpha() are used mostly for animation purpose, since 825 // Animator like to use alpha from 0 to 1. 826 public void setAlpha(float alpha) { 827 setRootAlpha((int) (alpha * 255)); 828 } 829 830 @SuppressWarnings("unused") 831 public float getAlpha() { 832 return getRootAlpha() / 255.0f; 833 } 834 835 public VPathRenderer(VPathRenderer copy) { 836 mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap); 837 mPath = new Path(copy.mPath); 838 mRenderPath = new Path(copy.mRenderPath); 839 mBaseWidth = copy.mBaseWidth; 840 mBaseHeight = copy.mBaseHeight; 841 mViewportWidth = copy.mViewportWidth; 842 mViewportHeight = copy.mViewportHeight; 843 mChangingConfigurations = copy.mChangingConfigurations; 844 mRootAlpha = copy.mRootAlpha; 845 mRootName = copy.mRootName; 846 if (copy.mRootName != null) { 847 mVGTargetsMap.put(copy.mRootName, this); 848 } 849 } 850 851 public boolean canApplyTheme() { 852 // If one of the paths can apply theme, then return true; 853 return recursiveCanApplyTheme(mRootGroup); 854 } 855 856 private boolean recursiveCanApplyTheme(VGroup currentGroup) { 857 // We can do a tree traverse here, if there is one path return true, 858 // then we return true for the whole tree. 859 final ArrayList<Object> children = currentGroup.mChildren; 860 861 for (int i = 0; i < children.size(); i++) { 862 Object child = children.get(i); 863 if (child instanceof VGroup) { 864 VGroup childGroup = (VGroup) child; 865 if (childGroup.canApplyTheme() 866 || recursiveCanApplyTheme(childGroup)) { 867 return true; 868 } 869 } else if (child instanceof VPath) { 870 VPath childPath = (VPath) child; 871 if (childPath.canApplyTheme()) { 872 return true; 873 } 874 } 875 } 876 return false; 877 } 878 879 public void applyTheme(Theme t) { 880 // Apply theme to every path of the tree. 881 recursiveApplyTheme(mRootGroup, t); 882 } 883 884 private void recursiveApplyTheme(VGroup currentGroup, Theme t) { 885 // We can do a tree traverse here, apply theme to all paths which 886 // can apply theme. 887 final ArrayList<Object> children = currentGroup.mChildren; 888 for (int i = 0; i < children.size(); i++) { 889 Object child = children.get(i); 890 if (child instanceof VGroup) { 891 VGroup childGroup = (VGroup) child; 892 if (childGroup.canApplyTheme()) { 893 childGroup.applyTheme(t); 894 } 895 recursiveApplyTheme(childGroup, t); 896 } else if (child instanceof VPath) { 897 VPath childPath = (VPath) child; 898 if (childPath.canApplyTheme()) { 899 childPath.applyTheme(t); 900 } 901 } 902 } 903 } 904 905 private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix, 906 Canvas canvas, int w, int h, ColorFilter filter) { 907 // Calculate current group's matrix by preConcat the parent's and 908 // and the current one on the top of the stack. 909 // Basically the Mfinal = Mviewport * M0 * M1 * M2; 910 // Mi the local matrix at level i of the group tree. 911 currentGroup.mStackedMatrix.set(currentMatrix); 912 913 currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix); 914 915 // Draw the group tree in the same order as the XML file. 916 for (int i = 0; i < currentGroup.mChildren.size(); i++) { 917 Object child = currentGroup.mChildren.get(i); 918 if (child instanceof VGroup) { 919 VGroup childGroup = (VGroup) child; 920 drawGroupTree(childGroup, currentGroup.mStackedMatrix, 921 canvas, w, h, filter); 922 } else if (child instanceof VPath) { 923 VPath childPath = (VPath) child; 924 drawPath(currentGroup, childPath, canvas, w, h, filter); 925 } 926 } 927 } 928 929 public void draw(Canvas canvas, int w, int h, ColorFilter filter) { 930 // Travese the tree in pre-order to draw. 931 drawGroupTree(mRootGroup, IDENTITY_MATRIX, canvas, w, h, filter); 932 } 933 934 private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h, 935 ColorFilter filter) { 936 final float scaleX = w / mViewportWidth; 937 final float scaleY = h / mViewportHeight; 938 final float minScale = Math.min(scaleX, scaleY); 939 940 mFinalPathMatrix.set(vGroup.mStackedMatrix); 941 mFinalPathMatrix.postScale(scaleX, scaleY); 942 943 vPath.toPath(mPath); 944 final Path path = mPath; 945 946 mRenderPath.reset(); 947 948 if (vPath.isClipPath()) { 949 mRenderPath.addPath(path, mFinalPathMatrix); 950 canvas.clipPath(mRenderPath, Region.Op.REPLACE); 951 } else { 952 VFullPath fullPath = (VFullPath) vPath; 953 if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) { 954 float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f; 955 float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f; 956 957 if (mPathMeasure == null) { 958 mPathMeasure = new PathMeasure(); 959 } 960 mPathMeasure.setPath(mPath, false); 961 962 float len = mPathMeasure.getLength(); 963 start = start * len; 964 end = end * len; 965 path.reset(); 966 if (start > end) { 967 mPathMeasure.getSegment(start, len, path, true); 968 mPathMeasure.getSegment(0f, end, path, true); 969 } else { 970 mPathMeasure.getSegment(start, end, path, true); 971 } 972 path.rLineTo(0, 0); // fix bug in measure 973 } 974 mRenderPath.addPath(path, mFinalPathMatrix); 975 976 if (fullPath.mFillColor != Color.TRANSPARENT) { 977 if (mFillPaint == null) { 978 mFillPaint = new Paint(); 979 mFillPaint.setStyle(Paint.Style.FILL); 980 mFillPaint.setAntiAlias(true); 981 } 982 983 final Paint fillPaint = mFillPaint; 984 fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha)); 985 fillPaint.setColorFilter(filter); 986 canvas.drawPath(mRenderPath, fillPaint); 987 } 988 989 if (fullPath.mStrokeColor != Color.TRANSPARENT) { 990 if (mStrokePaint == null) { 991 mStrokePaint = new Paint(); 992 mStrokePaint.setStyle(Paint.Style.STROKE); 993 mStrokePaint.setAntiAlias(true); 994 } 995 996 final Paint strokePaint = mStrokePaint; 997 if (fullPath.mStrokeLineJoin != null) { 998 strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin); 999 } 1000 1001 if (fullPath.mStrokeLineCap != null) { 1002 strokePaint.setStrokeCap(fullPath.mStrokeLineCap); 1003 } 1004 1005 strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit); 1006 strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha)); 1007 strokePaint.setColorFilter(filter); 1008 strokePaint.setStrokeWidth(fullPath.mStrokeWidth * minScale); 1009 canvas.drawPath(mRenderPath, strokePaint); 1010 } 1011 } 1012 } 1013 } 1014 1015 private static class VGroup { 1016 // mStackedMatrix is only used temporarily when drawing, it combines all 1017 // the parents' local matrices with the current one. 1018 private final Matrix mStackedMatrix = new Matrix(); 1019 1020 ///////////////////////////////////////////////////// 1021 // Variables below need to be copied (deep copy if applicable) for mutation. 1022 final ArrayList<Object> mChildren = new ArrayList<Object>(); 1023 1024 private float mRotate = 0; 1025 private float mPivotX = 0; 1026 private float mPivotY = 0; 1027 private float mScaleX = 1; 1028 private float mScaleY = 1; 1029 private float mTranslateX = 0; 1030 private float mTranslateY = 0; 1031 1032 // mLocalMatrix is updated based on the update of transformation information, 1033 // either parsed from the XML or by animation. 1034 private final Matrix mLocalMatrix = new Matrix(); 1035 private int mChangingConfigurations; 1036 private int[] mThemeAttrs; 1037 private String mGroupName = null; 1038 1039 public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) { 1040 mRotate = copy.mRotate; 1041 mPivotX = copy.mPivotX; 1042 mPivotY = copy.mPivotY; 1043 mScaleX = copy.mScaleX; 1044 mScaleY = copy.mScaleY; 1045 mTranslateX = copy.mTranslateX; 1046 mTranslateY = copy.mTranslateY; 1047 mThemeAttrs = copy.mThemeAttrs; 1048 mGroupName = copy.mGroupName; 1049 mChangingConfigurations = copy.mChangingConfigurations; 1050 if (mGroupName != null) { 1051 targetsMap.put(mGroupName, this); 1052 } 1053 1054 mLocalMatrix.set(copy.mLocalMatrix); 1055 1056 final ArrayList<Object> children = copy.mChildren; 1057 for (int i = 0; i < children.size(); i++) { 1058 Object copyChild = children.get(i); 1059 if (copyChild instanceof VGroup) { 1060 VGroup copyGroup = (VGroup) copyChild; 1061 mChildren.add(new VGroup(copyGroup, targetsMap)); 1062 } else { 1063 VPath newPath = null; 1064 if (copyChild instanceof VFullPath) { 1065 newPath = new VFullPath((VFullPath) copyChild); 1066 } else if (copyChild instanceof VClipPath) { 1067 newPath = new VClipPath((VClipPath) copyChild); 1068 } else { 1069 throw new IllegalStateException("Unknown object in the tree!"); 1070 } 1071 mChildren.add(newPath); 1072 if (newPath.mPathName != null) { 1073 targetsMap.put(newPath.mPathName, newPath); 1074 } 1075 } 1076 } 1077 } 1078 1079 public VGroup() { 1080 } 1081 1082 public String getGroupName() { 1083 return mGroupName; 1084 } 1085 1086 public Matrix getLocalMatrix() { 1087 return mLocalMatrix; 1088 } 1089 1090 public void inflate(Resources res, AttributeSet attrs, Theme theme) { 1091 final TypedArray a = obtainAttributes(res, theme, attrs, 1092 R.styleable.VectorDrawableGroup); 1093 updateStateFromTypedArray(a); 1094 a.recycle(); 1095 } 1096 1097 private void updateStateFromTypedArray(TypedArray a) { 1098 // Account for any configuration changes. 1099 mChangingConfigurations |= a.getChangingConfigurations(); 1100 1101 // Extract the theme attributes, if any. 1102 mThemeAttrs = a.extractThemeAttrs(); 1103 1104 mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate); 1105 mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX); 1106 mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY); 1107 mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX); 1108 mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY); 1109 mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX); 1110 mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); 1111 1112 final String groupName = a.getString(R.styleable.VectorDrawableGroup_name); 1113 if (groupName != null) { 1114 mGroupName = groupName; 1115 } 1116 1117 updateLocalMatrix(); 1118 } 1119 1120 public boolean canApplyTheme() { 1121 return mThemeAttrs != null; 1122 } 1123 1124 public void applyTheme(Theme t) { 1125 if (mThemeAttrs == null) { 1126 return; 1127 } 1128 1129 final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawableGroup); 1130 updateStateFromTypedArray(a); 1131 a.recycle(); 1132 } 1133 1134 private void updateLocalMatrix() { 1135 // The order we apply is the same as the 1136 // RenderNode.cpp::applyViewPropertyTransforms(). 1137 mLocalMatrix.reset(); 1138 mLocalMatrix.postTranslate(-mPivotX, -mPivotY); 1139 mLocalMatrix.postScale(mScaleX, mScaleY); 1140 mLocalMatrix.postRotate(mRotate, 0, 0); 1141 mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); 1142 } 1143 1144 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 1145 @SuppressWarnings("unused") 1146 public float getRotation() { 1147 return mRotate; 1148 } 1149 1150 @SuppressWarnings("unused") 1151 public void setRotation(float rotation) { 1152 if (rotation != mRotate) { 1153 mRotate = rotation; 1154 updateLocalMatrix(); 1155 } 1156 } 1157 1158 @SuppressWarnings("unused") 1159 public float getPivotX() { 1160 return mPivotX; 1161 } 1162 1163 @SuppressWarnings("unused") 1164 public void setPivotX(float pivotX) { 1165 if (pivotX != mPivotX) { 1166 mPivotX = pivotX; 1167 updateLocalMatrix(); 1168 } 1169 } 1170 1171 @SuppressWarnings("unused") 1172 public float getPivotY() { 1173 return mPivotY; 1174 } 1175 1176 @SuppressWarnings("unused") 1177 public void setPivotY(float pivotY) { 1178 if (pivotY != mPivotY) { 1179 mPivotY = pivotY; 1180 updateLocalMatrix(); 1181 } 1182 } 1183 1184 @SuppressWarnings("unused") 1185 public float getScaleX() { 1186 return mScaleX; 1187 } 1188 1189 @SuppressWarnings("unused") 1190 public void setScaleX(float scaleX) { 1191 if (scaleX != mScaleX) { 1192 mScaleX = scaleX; 1193 updateLocalMatrix(); 1194 } 1195 } 1196 1197 @SuppressWarnings("unused") 1198 public float getScaleY() { 1199 return mScaleY; 1200 } 1201 1202 @SuppressWarnings("unused") 1203 public void setScaleY(float scaleY) { 1204 if (scaleY != mScaleY) { 1205 mScaleY = scaleY; 1206 updateLocalMatrix(); 1207 } 1208 } 1209 1210 @SuppressWarnings("unused") 1211 public float getTranslateX() { 1212 return mTranslateX; 1213 } 1214 1215 @SuppressWarnings("unused") 1216 public void setTranslateX(float translateX) { 1217 if (translateX != mTranslateX) { 1218 mTranslateX = translateX; 1219 updateLocalMatrix(); 1220 } 1221 } 1222 1223 @SuppressWarnings("unused") 1224 public float getTranslateY() { 1225 return mTranslateY; 1226 } 1227 1228 @SuppressWarnings("unused") 1229 public void setTranslateY(float translateY) { 1230 if (translateY != mTranslateY) { 1231 mTranslateY = translateY; 1232 updateLocalMatrix(); 1233 } 1234 } 1235 } 1236 1237 /** 1238 * Common Path information for clip path and normal path. 1239 */ 1240 private static class VPath { 1241 protected PathParser.PathDataNode[] mNodes = null; 1242 String mPathName; 1243 int mChangingConfigurations; 1244 1245 public VPath() { 1246 // Empty constructor. 1247 } 1248 1249 public VPath(VPath copy) { 1250 mPathName = copy.mPathName; 1251 mChangingConfigurations = copy.mChangingConfigurations; 1252 mNodes = PathParser.deepCopyNodes(copy.mNodes); 1253 } 1254 1255 public void toPath(Path path) { 1256 path.reset(); 1257 if (mNodes != null) { 1258 PathParser.PathDataNode.nodesToPath(mNodes, path); 1259 } 1260 } 1261 1262 public String getPathName() { 1263 return mPathName; 1264 } 1265 1266 public boolean canApplyTheme() { 1267 return false; 1268 } 1269 1270 public void applyTheme(Theme t) { 1271 } 1272 1273 public boolean isClipPath() { 1274 return false; 1275 } 1276 1277 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 1278 @SuppressWarnings("unused") 1279 public PathParser.PathDataNode[] getPathData() { 1280 return mNodes; 1281 } 1282 1283 @SuppressWarnings("unused") 1284 public void setPathData(PathParser.PathDataNode[] nodes) { 1285 if (!PathParser.canMorph(mNodes, nodes)) { 1286 // This should not happen in the middle of animation. 1287 mNodes = PathParser.deepCopyNodes(nodes); 1288 } else { 1289 PathParser.updateNodes(mNodes, nodes); 1290 } 1291 } 1292 } 1293 1294 /** 1295 * Clip path, which only has name and pathData. 1296 */ 1297 private static class VClipPath extends VPath { 1298 public VClipPath() { 1299 // Empty constructor. 1300 } 1301 1302 public VClipPath(VClipPath copy) { 1303 super(copy); 1304 } 1305 1306 public void inflate(Resources r, AttributeSet attrs, Theme theme) { 1307 final TypedArray a = obtainAttributes(r, theme, attrs, 1308 R.styleable.VectorDrawableClipPath); 1309 updateStateFromTypedArray(a); 1310 a.recycle(); 1311 } 1312 1313 private void updateStateFromTypedArray(TypedArray a) { 1314 // Account for any configuration changes. 1315 mChangingConfigurations |= a.getChangingConfigurations(); 1316 1317 final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name); 1318 if (pathName != null) { 1319 mPathName = pathName; 1320 } 1321 1322 final String pathData = a.getString(R.styleable.VectorDrawableClipPath_pathData); 1323 if (pathData != null) { 1324 mNodes = PathParser.createNodesFromPathData(pathData); 1325 } 1326 } 1327 1328 @Override 1329 public boolean isClipPath() { 1330 return true; 1331 } 1332 } 1333 1334 /** 1335 * Normal path, which contains all the fill / paint information. 1336 */ 1337 private static class VFullPath extends VPath { 1338 ///////////////////////////////////////////////////// 1339 // Variables below need to be copied (deep copy if applicable) for mutation. 1340 private int[] mThemeAttrs; 1341 1342 int mStrokeColor = Color.TRANSPARENT; 1343 float mStrokeWidth = 0; 1344 1345 int mFillColor = Color.TRANSPARENT; 1346 float mStrokeAlpha = 1.0f; 1347 int mFillRule; 1348 float mFillAlpha = 1.0f; 1349 float mTrimPathStart = 0; 1350 float mTrimPathEnd = 1; 1351 float mTrimPathOffset = 0; 1352 1353 Paint.Cap mStrokeLineCap = Paint.Cap.BUTT; 1354 Paint.Join mStrokeLineJoin = Paint.Join.MITER; 1355 float mStrokeMiterlimit = 4; 1356 1357 public VFullPath() { 1358 // Empty constructor. 1359 } 1360 1361 public VFullPath(VFullPath copy) { 1362 super(copy); 1363 mThemeAttrs = copy.mThemeAttrs; 1364 1365 mStrokeColor = copy.mStrokeColor; 1366 mStrokeWidth = copy.mStrokeWidth; 1367 mStrokeAlpha = copy.mStrokeAlpha; 1368 mFillColor = copy.mFillColor; 1369 mFillRule = copy.mFillRule; 1370 mFillAlpha = copy.mFillAlpha; 1371 mTrimPathStart = copy.mTrimPathStart; 1372 mTrimPathEnd = copy.mTrimPathEnd; 1373 mTrimPathOffset = copy.mTrimPathOffset; 1374 1375 mStrokeLineCap = copy.mStrokeLineCap; 1376 mStrokeLineJoin = copy.mStrokeLineJoin; 1377 mStrokeMiterlimit = copy.mStrokeMiterlimit; 1378 } 1379 1380 private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) { 1381 switch (id) { 1382 case LINECAP_BUTT: 1383 return Paint.Cap.BUTT; 1384 case LINECAP_ROUND: 1385 return Paint.Cap.ROUND; 1386 case LINECAP_SQUARE: 1387 return Paint.Cap.SQUARE; 1388 default: 1389 return defValue; 1390 } 1391 } 1392 1393 private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) { 1394 switch (id) { 1395 case LINEJOIN_MITER: 1396 return Paint.Join.MITER; 1397 case LINEJOIN_ROUND: 1398 return Paint.Join.ROUND; 1399 case LINEJOIN_BEVEL: 1400 return Paint.Join.BEVEL; 1401 default: 1402 return defValue; 1403 } 1404 } 1405 1406 @Override 1407 public boolean canApplyTheme() { 1408 return mThemeAttrs != null; 1409 } 1410 1411 public void inflate(Resources r, AttributeSet attrs, Theme theme) { 1412 final TypedArray a = obtainAttributes(r, theme, attrs, 1413 R.styleable.VectorDrawablePath); 1414 updateStateFromTypedArray(a); 1415 a.recycle(); 1416 } 1417 1418 private void updateStateFromTypedArray(TypedArray a) { 1419 // Account for any configuration changes. 1420 mChangingConfigurations |= a.getChangingConfigurations(); 1421 1422 // Extract the theme attributes, if any. 1423 mThemeAttrs = a.extractThemeAttrs(); 1424 1425 final String pathName = a.getString(R.styleable.VectorDrawablePath_name); 1426 if (pathName != null) { 1427 mPathName = pathName; 1428 } 1429 1430 final String pathData = a.getString(R.styleable.VectorDrawablePath_pathData); 1431 if (pathData != null) { 1432 mNodes = PathParser.createNodesFromPathData(pathData); 1433 } 1434 1435 mFillColor = a.getColor(R.styleable.VectorDrawablePath_fillColor, 1436 mFillColor); 1437 mFillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, 1438 mFillAlpha); 1439 mStrokeLineCap = getStrokeLineCap(a.getInt( 1440 R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap); 1441 mStrokeLineJoin = getStrokeLineJoin(a.getInt( 1442 R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin); 1443 mStrokeMiterlimit = a.getFloat( 1444 R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit); 1445 mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_strokeColor, 1446 mStrokeColor); 1447 mStrokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha, 1448 mStrokeAlpha); 1449 mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, 1450 mStrokeWidth); 1451 mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, 1452 mTrimPathEnd); 1453 mTrimPathOffset = a.getFloat( 1454 R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset); 1455 mTrimPathStart = a.getFloat( 1456 R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart); 1457 } 1458 1459 @Override 1460 public void applyTheme(Theme t) { 1461 if (mThemeAttrs == null) { 1462 return; 1463 } 1464 1465 final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath); 1466 updateStateFromTypedArray(a); 1467 a.recycle(); 1468 } 1469 1470 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 1471 @SuppressWarnings("unused") 1472 int getStrokeColor() { 1473 return mStrokeColor; 1474 } 1475 1476 @SuppressWarnings("unused") 1477 void setStrokeColor(int strokeColor) { 1478 mStrokeColor = strokeColor; 1479 } 1480 1481 @SuppressWarnings("unused") 1482 float getStrokeWidth() { 1483 return mStrokeWidth; 1484 } 1485 1486 @SuppressWarnings("unused") 1487 void setStrokeWidth(float strokeWidth) { 1488 mStrokeWidth = strokeWidth; 1489 } 1490 1491 @SuppressWarnings("unused") 1492 float getStrokeAlpha() { 1493 return mStrokeAlpha; 1494 } 1495 1496 @SuppressWarnings("unused") 1497 void setStrokeAlpha(float strokeAlpha) { 1498 mStrokeAlpha = strokeAlpha; 1499 } 1500 1501 @SuppressWarnings("unused") 1502 int getFillColor() { 1503 return mFillColor; 1504 } 1505 1506 @SuppressWarnings("unused") 1507 void setFillColor(int fillColor) { 1508 mFillColor = fillColor; 1509 } 1510 1511 @SuppressWarnings("unused") 1512 float getFillAlpha() { 1513 return mFillAlpha; 1514 } 1515 1516 @SuppressWarnings("unused") 1517 void setFillAlpha(float fillAlpha) { 1518 mFillAlpha = fillAlpha; 1519 } 1520 1521 @SuppressWarnings("unused") 1522 float getTrimPathStart() { 1523 return mTrimPathStart; 1524 } 1525 1526 @SuppressWarnings("unused") 1527 void setTrimPathStart(float trimPathStart) { 1528 mTrimPathStart = trimPathStart; 1529 } 1530 1531 @SuppressWarnings("unused") 1532 float getTrimPathEnd() { 1533 return mTrimPathEnd; 1534 } 1535 1536 @SuppressWarnings("unused") 1537 void setTrimPathEnd(float trimPathEnd) { 1538 mTrimPathEnd = trimPathEnd; 1539 } 1540 1541 @SuppressWarnings("unused") 1542 float getTrimPathOffset() { 1543 return mTrimPathOffset; 1544 } 1545 1546 @SuppressWarnings("unused") 1547 void setTrimPathOffset(float trimPathOffset) { 1548 mTrimPathOffset = trimPathOffset; 1549 } 1550 } 1551} 1552