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