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