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