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