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