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