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