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