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