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