VectorDrawable.java revision 46591f4a2dbd785bcae2b82bb490e78208605ec8
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15package android.graphics.drawable; 16 17import android.annotation.NonNull; 18import android.annotation.Nullable; 19import android.content.pm.ActivityInfo.Config; 20import android.content.res.ColorStateList; 21import android.content.res.ComplexColor; 22import android.content.res.GradientColor; 23import android.content.res.Resources; 24import android.content.res.Resources.Theme; 25import android.content.res.TypedArray; 26import android.graphics.Canvas; 27import android.graphics.ColorFilter; 28import android.graphics.Insets; 29import android.graphics.PixelFormat; 30import android.graphics.PorterDuffColorFilter; 31import android.graphics.Rect; 32import android.graphics.PorterDuff.Mode; 33import android.graphics.Shader; 34import android.util.ArrayMap; 35import android.util.AttributeSet; 36import android.util.DisplayMetrics; 37import android.util.LayoutDirection; 38import android.util.Log; 39import android.util.PathParser; 40import android.util.Xml; 41 42import com.android.internal.R; 43import com.android.internal.util.VirtualRefBasePtr; 44 45import org.xmlpull.v1.XmlPullParser; 46import org.xmlpull.v1.XmlPullParserException; 47 48import java.io.IOException; 49import java.nio.ByteBuffer; 50import java.nio.ByteOrder; 51import java.util.ArrayList; 52import java.util.HashMap; 53import java.util.Stack; 54 55/** 56 * This lets you create a drawable based on an XML vector graphic. It can be 57 * defined in an XML file with the <code><vector></code> element. 58 * <p/> 59 * The vector drawable has the following elements: 60 * <p/> 61 * <dt><code><vector></code></dt> 62 * <dl> 63 * <dd>Used to define a vector drawable 64 * <dl> 65 * <dt><code>android:name</code></dt> 66 * <dd>Defines the name of this vector drawable.</dd> 67 * <dt><code>android:width</code></dt> 68 * <dd>Used to define the intrinsic width of the drawable. 69 * This support all the dimension units, normally specified with dp.</dd> 70 * <dt><code>android:height</code></dt> 71 * <dd>Used to define the intrinsic height the drawable. 72 * This support all the dimension units, normally specified with dp.</dd> 73 * <dt><code>android:viewportWidth</code></dt> 74 * <dd>Used to define the width of the viewport space. Viewport is basically 75 * the virtual canvas where the paths are drawn on.</dd> 76 * <dt><code>android:viewportHeight</code></dt> 77 * <dd>Used to define the height of the viewport space. Viewport is basically 78 * the virtual canvas where the paths are drawn on.</dd> 79 * <dt><code>android:tint</code></dt> 80 * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd> 81 * <dt><code>android:tintMode</code></dt> 82 * <dd>The Porter-Duff blending mode for the tint color. The default value is src_in.</dd> 83 * <dt><code>android:autoMirrored</code></dt> 84 * <dd>Indicates if the drawable needs to be mirrored when its layout direction is 85 * RTL (right-to-left).</dd> 86 * <dt><code>android:alpha</code></dt> 87 * <dd>The opacity of this drawable.</dd> 88 * </dl></dd> 89 * </dl> 90 * 91 * <dl> 92 * <dt><code><group></code></dt> 93 * <dd>Defines a group of paths or subgroups, plus transformation information. 94 * The transformations are defined in the same coordinates as the viewport. 95 * And the transformations are applied in the order of scale, rotate then translate. 96 * <dl> 97 * <dt><code>android:name</code></dt> 98 * <dd>Defines the name of the group.</dd> 99 * <dt><code>android:rotation</code></dt> 100 * <dd>The degrees of rotation of the group.</dd> 101 * <dt><code>android:pivotX</code></dt> 102 * <dd>The X coordinate of the pivot for the scale and rotation of the group. 103 * This is defined in the viewport space.</dd> 104 * <dt><code>android:pivotY</code></dt> 105 * <dd>The Y coordinate of the pivot for the scale and rotation of the group. 106 * This is defined in the viewport space.</dd> 107 * <dt><code>android:scaleX</code></dt> 108 * <dd>The amount of scale on the X Coordinate.</dd> 109 * <dt><code>android:scaleY</code></dt> 110 * <dd>The amount of scale on the Y coordinate.</dd> 111 * <dt><code>android:translateX</code></dt> 112 * <dd>The amount of translation on the X coordinate. 113 * This is defined in the viewport space.</dd> 114 * <dt><code>android:translateY</code></dt> 115 * <dd>The amount of translation on the Y coordinate. 116 * This is defined in the viewport space.</dd> 117 * </dl></dd> 118 * </dl> 119 * 120 * <dl> 121 * <dt><code><path></code></dt> 122 * <dd>Defines paths to be drawn. 123 * <dl> 124 * <dt><code>android:name</code></dt> 125 * <dd>Defines the name of the path.</dd> 126 * <dt><code>android:pathData</code></dt> 127 * <dd>Defines path data using exactly same format as "d" attribute 128 * in the SVG's path data. This is defined in the viewport space.</dd> 129 * <dt><code>android:fillColor</code></dt> 130 * <dd>Specifies the color used to fill the path. May be a color, also may be a color state list or 131 * a gradient color for SDK 24+. If this property is animated, any value set by the animation will 132 * override the original value. No path fill is drawn if this property is not specified.</dd> 133 * <dt><code>android:strokeColor</code></dt> 134 * <dd>Specifies the color used to draw the path outline. May be a color or (SDK 24+ only) a color 135 * state list. If this property is animated, any value set by the animation will override the 136 * original value. No path outline is drawn if this property is not specified.</dd> 137 * <dt><code>android:strokeWidth</code></dt> 138 * <dd>The width a path stroke.</dd> 139 * <dt><code>android:strokeAlpha</code></dt> 140 * <dd>The opacity of a path stroke.</dd> 141 * <dt><code>android:fillAlpha</code></dt> 142 * <dd>The opacity to fill the path with.</dd> 143 * <dt><code>android:trimPathStart</code></dt> 144 * <dd>The fraction of the path to trim from the start, in the range from 0 to 1.</dd> 145 * <dt><code>android:trimPathEnd</code></dt> 146 * <dd>The fraction of the path to trim from the end, in the range from 0 to 1.</dd> 147 * <dt><code>android:trimPathOffset</code></dt> 148 * <dd>Shift trim region (allows showed region to include the start and end), in the range 149 * from 0 to 1.</dd> 150 * <dt><code>android:strokeLineCap</code></dt> 151 * <dd>Sets the linecap for a stroked path: butt, round, square.</dd> 152 * <dt><code>android:strokeLineJoin</code></dt> 153 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel.</dd> 154 * <dt><code>android:strokeMiterLimit</code></dt> 155 * <dd>Sets the Miter limit for a stroked path.</dd> 156 * </dl></dd> 157 * </dl> 158 * 159 * <dl> 160 * <dt><code><clip-path></code></dt> 161 * <dd>Defines path to be the current clip. Note that the clip path only apply to 162 * the current group and its children. 163 * <dl> 164 * <dt><code>android:name</code></dt> 165 * <dd>Defines the name of the clip path.</dd> 166 * <dt><code>android:pathData</code></dt> 167 * <dd>Defines clip path using the same format as "d" attribute 168 * in the SVG's path data.</dd> 169 * </dl></dd> 170 * </dl> 171 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file. 172 * <pre> 173 * <vector xmlns:android="http://schemas.android.com/apk/res/android" 174 * android:height="64dp" 175 * android:width="64dp" 176 * android:viewportHeight="600" 177 * android:viewportWidth="600" > 178 * <group 179 * android:name="rotationGroup" 180 * android:pivotX="300.0" 181 * android:pivotY="300.0" 182 * android:rotation="45.0" > 183 * <path 184 * android:name="v" 185 * android:fillColor="#000000" 186 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 187 * </group> 188 * </vector> 189 * </pre></li> 190 */ 191 192public class VectorDrawable extends Drawable { 193 private static final String LOGTAG = VectorDrawable.class.getSimpleName(); 194 195 private static final String SHAPE_CLIP_PATH = "clip-path"; 196 private static final String SHAPE_GROUP = "group"; 197 private static final String SHAPE_PATH = "path"; 198 private static final String SHAPE_VECTOR = "vector"; 199 200 private VectorDrawableState mVectorState; 201 202 private PorterDuffColorFilter mTintFilter; 203 private ColorFilter mColorFilter; 204 205 private boolean mMutated; 206 207 /** The density of the display on which this drawable will be rendered. */ 208 private int mTargetDensity; 209 210 // Given the virtual display setup, the dpi can be different than the inflation's dpi. 211 // Therefore, we need to scale the values we got from the getDimension*(). 212 private int mDpiScaledWidth = 0; 213 private int mDpiScaledHeight = 0; 214 private Insets mDpiScaledInsets = Insets.NONE; 215 216 /** Whether DPI-scaled width, height, and insets need to be updated. */ 217 private boolean mDpiScaledDirty = true; 218 219 // Temp variable, only for saving "new" operation at the draw() time. 220 private final Rect mTmpBounds = new Rect(); 221 222 public VectorDrawable() { 223 this(new VectorDrawableState(), null); 224 } 225 226 /** 227 * The one constructor to rule them all. This is called by all public 228 * constructors to set the state and initialize local properties. 229 */ 230 private VectorDrawable(@NonNull VectorDrawableState state, @Nullable Resources res) { 231 mVectorState = state; 232 updateLocalState(res); 233 } 234 235 /** 236 * Initializes local dynamic properties from state. This should be called 237 * after significant state changes, e.g. from the One True Constructor and 238 * after inflating or applying a theme. 239 * 240 * @param res resources of the context in which the drawable will be 241 * displayed, or {@code null} to use the constant state defaults 242 */ 243 private void updateLocalState(Resources res) { 244 final int density = Drawable.resolveDensity(res, mVectorState.mDensity); 245 if (mTargetDensity != density) { 246 mTargetDensity = density; 247 mDpiScaledDirty = true; 248 } 249 250 mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode); 251 } 252 253 @Override 254 public Drawable mutate() { 255 if (!mMutated && super.mutate() == this) { 256 mVectorState = new VectorDrawableState(mVectorState); 257 mMutated = true; 258 } 259 return this; 260 } 261 262 /** 263 * @hide 264 */ 265 public void clearMutated() { 266 super.clearMutated(); 267 mMutated = false; 268 } 269 270 Object getTargetByName(String name) { 271 return mVectorState.mVGTargetsMap.get(name); 272 } 273 274 @Override 275 public ConstantState getConstantState() { 276 mVectorState.mChangingConfigurations = getChangingConfigurations(); 277 return mVectorState; 278 } 279 280 @Override 281 public void draw(Canvas canvas) { 282 // We will offset the bounds for drawBitmap, so copyBounds() here instead 283 // of getBounds(). 284 copyBounds(mTmpBounds); 285 if (mTmpBounds.width() <= 0 || mTmpBounds.height() <= 0) { 286 // Nothing to draw 287 return; 288 } 289 290 // Color filters always override tint filters. 291 final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter); 292 final long colorFilterNativeInstance = colorFilter == null ? 0 : 293 colorFilter.native_instance; 294 boolean canReuseCache = mVectorState.canReuseCache(); 295 nDraw(mVectorState.getNativeRenderer(), canvas.getNativeCanvasWrapper(), 296 colorFilterNativeInstance, mTmpBounds, needMirroring(), 297 canReuseCache); 298 } 299 300 301 @Override 302 public int getAlpha() { 303 return (int) (mVectorState.getAlpha() * 255); 304 } 305 306 @Override 307 public void setAlpha(int alpha) { 308 if (mVectorState.setAlpha(alpha / 255f)) { 309 invalidateSelf(); 310 } 311 } 312 313 @Override 314 public void setColorFilter(ColorFilter colorFilter) { 315 mColorFilter = colorFilter; 316 invalidateSelf(); 317 } 318 319 @Override 320 public ColorFilter getColorFilter() { 321 return mColorFilter; 322 } 323 324 @Override 325 public void setTintList(ColorStateList tint) { 326 final VectorDrawableState state = mVectorState; 327 if (state.mTint != tint) { 328 state.mTint = tint; 329 mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode); 330 invalidateSelf(); 331 } 332 } 333 334 @Override 335 public void setTintMode(Mode tintMode) { 336 final VectorDrawableState state = mVectorState; 337 if (state.mTintMode != tintMode) { 338 state.mTintMode = tintMode; 339 mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode); 340 invalidateSelf(); 341 } 342 } 343 344 @Override 345 public boolean isStateful() { 346 return super.isStateful() || (mVectorState != null && mVectorState.isStateful()); 347 } 348 349 @Override 350 protected boolean onStateChange(int[] stateSet) { 351 boolean changed = false; 352 353 final VectorDrawableState state = mVectorState; 354 if (state.onStateChange(stateSet)) { 355 changed = true; 356 state.mCacheDirty = true; 357 } 358 if (state.mTint != null && state.mTintMode != null) { 359 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 360 changed = true; 361 } 362 363 return changed; 364 } 365 366 @Override 367 public int getOpacity() { 368 // We can't tell whether the drawable is fully opaque unless we examine all the pixels, 369 // but we could tell it is transparent if the root alpha is 0. 370 return getAlpha() == 0 ? PixelFormat.TRANSPARENT : PixelFormat.TRANSLUCENT; 371 } 372 373 @Override 374 public int getIntrinsicWidth() { 375 if (mDpiScaledDirty) { 376 computeVectorSize(); 377 } 378 return mDpiScaledWidth; 379 } 380 381 @Override 382 public int getIntrinsicHeight() { 383 if (mDpiScaledDirty) { 384 computeVectorSize(); 385 } 386 return mDpiScaledHeight; 387 } 388 389 /** @hide */ 390 @Override 391 public Insets getOpticalInsets() { 392 if (mDpiScaledDirty) { 393 computeVectorSize(); 394 } 395 return mDpiScaledInsets; 396 } 397 398 /* 399 * Update local dimensions to adjust for a target density that may differ 400 * from the source density against which the constant state was loaded. 401 */ 402 void computeVectorSize() { 403 final Insets opticalInsets = mVectorState.mOpticalInsets; 404 405 final int sourceDensity = mVectorState.mDensity; 406 final int targetDensity = mTargetDensity; 407 if (targetDensity != sourceDensity) { 408 mDpiScaledWidth = Drawable.scaleFromDensity( 409 (int) mVectorState.mBaseWidth, sourceDensity, targetDensity, true); 410 mDpiScaledHeight = Drawable.scaleFromDensity( 411 (int) mVectorState.mBaseHeight,sourceDensity, targetDensity, true); 412 final int left = Drawable.scaleFromDensity( 413 opticalInsets.left, sourceDensity, targetDensity, false); 414 final int right = Drawable.scaleFromDensity( 415 opticalInsets.right, sourceDensity, targetDensity, false); 416 final int top = Drawable.scaleFromDensity( 417 opticalInsets.top, sourceDensity, targetDensity, false); 418 final int bottom = Drawable.scaleFromDensity( 419 opticalInsets.bottom, sourceDensity, targetDensity, false); 420 mDpiScaledInsets = Insets.of(left, top, right, bottom); 421 } else { 422 mDpiScaledWidth = (int) mVectorState.mBaseWidth; 423 mDpiScaledHeight = (int) mVectorState.mBaseHeight; 424 mDpiScaledInsets = opticalInsets; 425 } 426 427 mDpiScaledDirty = false; 428 } 429 430 @Override 431 public boolean canApplyTheme() { 432 return (mVectorState != null && mVectorState.canApplyTheme()) || super.canApplyTheme(); 433 } 434 435 @Override 436 public void applyTheme(Theme t) { 437 super.applyTheme(t); 438 439 final VectorDrawableState state = mVectorState; 440 if (state == null) { 441 return; 442 } 443 444 final boolean changedDensity = mVectorState.setDensity( 445 Drawable.resolveDensity(t.getResources(), 0)); 446 mDpiScaledDirty |= changedDensity; 447 448 if (state.mThemeAttrs != null) { 449 final TypedArray a = t.resolveAttributes( 450 state.mThemeAttrs, R.styleable.VectorDrawable); 451 try { 452 state.mCacheDirty = true; 453 updateStateFromTypedArray(a); 454 } catch (XmlPullParserException e) { 455 throw new RuntimeException(e); 456 } finally { 457 a.recycle(); 458 } 459 460 // May have changed size. 461 mDpiScaledDirty = true; 462 } 463 464 // Apply theme to contained color state list. 465 if (state.mTint != null && state.mTint.canApplyTheme()) { 466 state.mTint = state.mTint.obtainForTheme(t); 467 } 468 469 if (mVectorState != null && mVectorState.canApplyTheme()) { 470 mVectorState.applyTheme(t); 471 } 472 473 // Update local properties. 474 updateLocalState(t.getResources()); 475 } 476 477 /** 478 * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension. 479 * This is used to calculate the path animation accuracy. 480 * 481 * @hide 482 */ 483 public float getPixelSize() { 484 if (mVectorState == null || 485 mVectorState.mBaseWidth == 0 || 486 mVectorState.mBaseHeight == 0 || 487 mVectorState.mViewportHeight == 0 || 488 mVectorState.mViewportWidth == 0) { 489 return 1; // fall back to 1:1 pixel mapping. 490 } 491 float intrinsicWidth = mVectorState.mBaseWidth; 492 float intrinsicHeight = mVectorState.mBaseHeight; 493 float viewportWidth = mVectorState.mViewportWidth; 494 float viewportHeight = mVectorState.mViewportHeight; 495 float scaleX = viewportWidth / intrinsicWidth; 496 float scaleY = viewportHeight / intrinsicHeight; 497 return Math.min(scaleX, scaleY); 498 } 499 500 /** @hide */ 501 public static VectorDrawable create(Resources resources, int rid) { 502 try { 503 final XmlPullParser parser = resources.getXml(rid); 504 final AttributeSet attrs = Xml.asAttributeSet(parser); 505 int type; 506 while ((type=parser.next()) != XmlPullParser.START_TAG && 507 type != XmlPullParser.END_DOCUMENT) { 508 // Empty loop 509 } 510 if (type != XmlPullParser.START_TAG) { 511 throw new XmlPullParserException("No start tag found"); 512 } 513 514 final VectorDrawable drawable = new VectorDrawable(); 515 drawable.inflate(resources, parser, attrs); 516 517 return drawable; 518 } catch (XmlPullParserException e) { 519 Log.e(LOGTAG, "parser error", e); 520 } catch (IOException e) { 521 Log.e(LOGTAG, "parser error", e); 522 } 523 return null; 524 } 525 526 @Override 527 public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, 528 @NonNull AttributeSet attrs, @Nullable Theme theme) 529 throws XmlPullParserException, IOException { 530 if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererRefBase != null) { 531 // This VD has been used to display other VD resource content, clean up. 532 mVectorState.mRootGroup = new VGroup(); 533 if (mVectorState.mNativeRendererRefBase != null) { 534 mVectorState.mNativeRendererRefBase.release(); 535 } 536 mVectorState.createNativeRenderer(mVectorState.mRootGroup.mNativePtr); 537 } 538 final VectorDrawableState state = mVectorState; 539 state.setDensity(Drawable.resolveDensity(r, 0)); 540 541 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable); 542 updateStateFromTypedArray(a); 543 a.recycle(); 544 545 mDpiScaledDirty = true; 546 547 state.mCacheDirty = true; 548 inflateChildElements(r, parser, attrs, theme); 549 550 // Update local properties. 551 updateLocalState(r); 552 } 553 554 private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { 555 final VectorDrawableState state = mVectorState; 556 557 // Account for any configuration changes. 558 state.mChangingConfigurations |= a.getChangingConfigurations(); 559 560 // Extract the theme attributes, if any. 561 state.mThemeAttrs = a.extractThemeAttrs(); 562 563 final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1); 564 if (tintMode != -1) { 565 state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); 566 } 567 568 final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint); 569 if (tint != null) { 570 state.mTint = tint; 571 } 572 573 state.mAutoMirrored = a.getBoolean( 574 R.styleable.VectorDrawable_autoMirrored, state.mAutoMirrored); 575 576 float viewportWidth = a.getFloat( 577 R.styleable.VectorDrawable_viewportWidth, state.mViewportWidth); 578 float viewportHeight = a.getFloat( 579 R.styleable.VectorDrawable_viewportHeight, state.mViewportHeight); 580 state.setViewportSize(viewportWidth, viewportHeight); 581 582 if (state.mViewportWidth <= 0) { 583 throw new XmlPullParserException(a.getPositionDescription() + 584 "<vector> tag requires viewportWidth > 0"); 585 } else if (state.mViewportHeight <= 0) { 586 throw new XmlPullParserException(a.getPositionDescription() + 587 "<vector> tag requires viewportHeight > 0"); 588 } 589 590 state.mBaseWidth = a.getDimension( 591 R.styleable.VectorDrawable_width, state.mBaseWidth); 592 state.mBaseHeight = a.getDimension( 593 R.styleable.VectorDrawable_height, state.mBaseHeight); 594 595 if (state.mBaseWidth <= 0) { 596 throw new XmlPullParserException(a.getPositionDescription() + 597 "<vector> tag requires width > 0"); 598 } else if (state.mBaseHeight <= 0) { 599 throw new XmlPullParserException(a.getPositionDescription() + 600 "<vector> tag requires height > 0"); 601 } 602 603 final int insetLeft = a.getDimensionPixelOffset( 604 R.styleable.VectorDrawable_opticalInsetLeft, state.mOpticalInsets.left); 605 final int insetTop = a.getDimensionPixelOffset( 606 R.styleable.VectorDrawable_opticalInsetTop, state.mOpticalInsets.top); 607 final int insetRight = a.getDimensionPixelOffset( 608 R.styleable.VectorDrawable_opticalInsetRight, state.mOpticalInsets.right); 609 final int insetBottom = a.getDimensionPixelOffset( 610 R.styleable.VectorDrawable_opticalInsetBottom, state.mOpticalInsets.bottom); 611 state.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom); 612 613 final float alphaInFloat = a.getFloat( 614 R.styleable.VectorDrawable_alpha, state.getAlpha()); 615 state.setAlpha(alphaInFloat); 616 617 final String name = a.getString(R.styleable.VectorDrawable_name); 618 if (name != null) { 619 state.mRootName = name; 620 state.mVGTargetsMap.put(name, state); 621 } 622 } 623 624 private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs, 625 Theme theme) throws XmlPullParserException, IOException { 626 final VectorDrawableState state = mVectorState; 627 boolean noPathTag = true; 628 629 // Use a stack to help to build the group tree. 630 // The top of the stack is always the current group. 631 final Stack<VGroup> groupStack = new Stack<VGroup>(); 632 groupStack.push(state.mRootGroup); 633 634 int eventType = parser.getEventType(); 635 while (eventType != XmlPullParser.END_DOCUMENT) { 636 if (eventType == XmlPullParser.START_TAG) { 637 final String tagName = parser.getName(); 638 final VGroup currentGroup = groupStack.peek(); 639 640 if (SHAPE_PATH.equals(tagName)) { 641 final VFullPath path = new VFullPath(); 642 path.inflate(res, attrs, theme); 643 currentGroup.addChild(path); 644 if (path.getPathName() != null) { 645 state.mVGTargetsMap.put(path.getPathName(), path); 646 } 647 noPathTag = false; 648 state.mChangingConfigurations |= path.mChangingConfigurations; 649 } else if (SHAPE_CLIP_PATH.equals(tagName)) { 650 final VClipPath path = new VClipPath(); 651 path.inflate(res, attrs, theme); 652 currentGroup.addChild(path); 653 if (path.getPathName() != null) { 654 state.mVGTargetsMap.put(path.getPathName(), path); 655 } 656 state.mChangingConfigurations |= path.mChangingConfigurations; 657 } else if (SHAPE_GROUP.equals(tagName)) { 658 VGroup newChildGroup = new VGroup(); 659 newChildGroup.inflate(res, attrs, theme); 660 currentGroup.addChild(newChildGroup); 661 groupStack.push(newChildGroup); 662 if (newChildGroup.getGroupName() != null) { 663 state.mVGTargetsMap.put(newChildGroup.getGroupName(), 664 newChildGroup); 665 } 666 state.mChangingConfigurations |= newChildGroup.mChangingConfigurations; 667 } 668 } else if (eventType == XmlPullParser.END_TAG) { 669 final String tagName = parser.getName(); 670 if (SHAPE_GROUP.equals(tagName)) { 671 groupStack.pop(); 672 } 673 } 674 eventType = parser.next(); 675 } 676 677 if (noPathTag) { 678 final StringBuffer tag = new StringBuffer(); 679 680 if (tag.length() > 0) { 681 tag.append(" or "); 682 } 683 tag.append(SHAPE_PATH); 684 685 throw new XmlPullParserException("no " + tag + " defined"); 686 } 687 } 688 689 @Override 690 public @Config int getChangingConfigurations() { 691 return super.getChangingConfigurations() | mVectorState.getChangingConfigurations(); 692 } 693 694 void setAllowCaching(boolean allowCaching) { 695 nSetAllowCaching(mVectorState.getNativeRenderer(), allowCaching); 696 } 697 698 private boolean needMirroring() { 699 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 700 } 701 702 @Override 703 public void setAutoMirrored(boolean mirrored) { 704 if (mVectorState.mAutoMirrored != mirrored) { 705 mVectorState.mAutoMirrored = mirrored; 706 invalidateSelf(); 707 } 708 } 709 710 @Override 711 public boolean isAutoMirrored() { 712 return mVectorState.mAutoMirrored; 713 } 714 715 static class VectorDrawableState extends ConstantState { 716 // Variables below need to be copied (deep copy if applicable) for mutation. 717 int[] mThemeAttrs; 718 @Config int mChangingConfigurations; 719 ColorStateList mTint = null; 720 Mode mTintMode = DEFAULT_TINT_MODE; 721 boolean mAutoMirrored; 722 723 float mBaseWidth = 0; 724 float mBaseHeight = 0; 725 float mViewportWidth = 0; 726 float mViewportHeight = 0; 727 Insets mOpticalInsets = Insets.NONE; 728 String mRootName = null; 729 VGroup mRootGroup; 730 VirtualRefBasePtr mNativeRendererRefBase = null; 731 732 int mDensity = DisplayMetrics.DENSITY_DEFAULT; 733 final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>(); 734 735 // Fields for cache 736 int[] mCachedThemeAttrs; 737 ColorStateList mCachedTint; 738 Mode mCachedTintMode; 739 boolean mCachedAutoMirrored; 740 boolean mCacheDirty; 741 742 // Deep copy for mutate() or implicitly mutate. 743 public VectorDrawableState(VectorDrawableState copy) { 744 if (copy != null) { 745 mThemeAttrs = copy.mThemeAttrs; 746 mChangingConfigurations = copy.mChangingConfigurations; 747 mTint = copy.mTint; 748 mTintMode = copy.mTintMode; 749 mAutoMirrored = copy.mAutoMirrored; 750 mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap); 751 createNativeRenderer(mRootGroup.mNativePtr); 752 753 mBaseWidth = copy.mBaseWidth; 754 mBaseHeight = copy.mBaseHeight; 755 setViewportSize(copy.mViewportWidth, copy.mViewportHeight); 756 mOpticalInsets = copy.mOpticalInsets; 757 758 mRootName = copy.mRootName; 759 mDensity = copy.mDensity; 760 if (copy.mRootName != null) { 761 mVGTargetsMap.put(copy.mRootName, this); 762 } 763 } 764 } 765 766 private void createNativeRenderer(long rootGroupPtr) { 767 mNativeRendererRefBase = new VirtualRefBasePtr(nCreateRenderer(rootGroupPtr)); 768 } 769 770 long getNativeRenderer() { 771 if (mNativeRendererRefBase == null) { 772 return 0; 773 } 774 return mNativeRendererRefBase.get(); 775 } 776 777 public boolean canReuseCache() { 778 if (!mCacheDirty 779 && mCachedThemeAttrs == mThemeAttrs 780 && mCachedTint == mTint 781 && mCachedTintMode == mTintMode 782 && mCachedAutoMirrored == mAutoMirrored) { 783 return true; 784 } 785 updateCacheStates(); 786 return false; 787 } 788 789 public void updateCacheStates() { 790 // Use shallow copy here and shallow comparison in canReuseCache(), 791 // likely hit cache miss more, but practically not much difference. 792 mCachedThemeAttrs = mThemeAttrs; 793 mCachedTint = mTint; 794 mCachedTintMode = mTintMode; 795 mCachedAutoMirrored = mAutoMirrored; 796 mCacheDirty = false; 797 } 798 799 public void applyTheme(Theme t) { 800 mRootGroup.applyTheme(t); 801 } 802 803 @Override 804 public boolean canApplyTheme() { 805 return mThemeAttrs != null 806 || (mRootGroup != null && mRootGroup.canApplyTheme()) 807 || (mTint != null && mTint.canApplyTheme()) 808 || super.canApplyTheme(); 809 } 810 811 public VectorDrawableState() { 812 mRootGroup = new VGroup(); 813 createNativeRenderer(mRootGroup.mNativePtr); 814 } 815 816 @Override 817 public Drawable newDrawable() { 818 return new VectorDrawable(this, null); 819 } 820 821 @Override 822 public Drawable newDrawable(Resources res) { 823 return new VectorDrawable(this, res); 824 } 825 826 @Override 827 public @Config int getChangingConfigurations() { 828 return mChangingConfigurations 829 | (mTint != null ? mTint.getChangingConfigurations() : 0); 830 } 831 832 public boolean isStateful() { 833 return (mTint != null && mTint.isStateful()) 834 || (mRootGroup != null && mRootGroup.isStateful()); 835 } 836 837 void setViewportSize(float viewportWidth, float viewportHeight) { 838 mViewportWidth = viewportWidth; 839 mViewportHeight = viewportHeight; 840 nSetRendererViewportSize(getNativeRenderer(), viewportWidth, viewportHeight); 841 } 842 843 public final boolean setDensity(int targetDensity) { 844 if (mDensity != targetDensity) { 845 final int sourceDensity = mDensity; 846 mDensity = targetDensity; 847 applyDensityScaling(sourceDensity, targetDensity); 848 return true; 849 } 850 return false; 851 } 852 853 private void applyDensityScaling(int sourceDensity, int targetDensity) { 854 mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity); 855 mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity); 856 857 final int insetLeft = Drawable.scaleFromDensity( 858 mOpticalInsets.left, sourceDensity, targetDensity, false); 859 final int insetTop = Drawable.scaleFromDensity( 860 mOpticalInsets.top, sourceDensity, targetDensity, false); 861 final int insetRight = Drawable.scaleFromDensity( 862 mOpticalInsets.right, sourceDensity, targetDensity, false); 863 final int insetBottom = Drawable.scaleFromDensity( 864 mOpticalInsets.bottom, sourceDensity, targetDensity, false); 865 mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom); 866 } 867 868 public boolean onStateChange(int[] stateSet) { 869 return mRootGroup.onStateChange(stateSet); 870 } 871 872 /** 873 * setAlpha() and getAlpha() are used mostly for animation purpose. Return true if alpha 874 * has changed. 875 */ 876 public boolean setAlpha(float alpha) { 877 return nSetRootAlpha(mNativeRendererRefBase.get(), alpha); 878 } 879 880 @SuppressWarnings("unused") 881 public float getAlpha() { 882 return nGetRootAlpha(mNativeRendererRefBase.get()); 883 } 884 } 885 886 static class VGroup implements VObject { 887 private static final int ROTATE_INDEX = 0; 888 private static final int PIVOT_X_INDEX = 1; 889 private static final int PIVOT_Y_INDEX = 2; 890 private static final int SCALE_X_INDEX = 3; 891 private static final int SCALE_Y_INDEX = 4; 892 private static final int TRANSLATE_X_INDEX = 5; 893 private static final int TRANSLATE_Y_INDEX = 6; 894 private static final int TRANSFORM_PROPERTY_COUNT = 7; 895 896 private static final HashMap<String, Integer> sPropertyMap = 897 new HashMap<String, Integer>() { 898 { 899 put("translateX", TRANSLATE_X_INDEX); 900 put("translateY", TRANSLATE_Y_INDEX); 901 put("scaleX", SCALE_X_INDEX); 902 put("scaleY", SCALE_Y_INDEX); 903 put("pivotX", PIVOT_X_INDEX); 904 put("pivotY", PIVOT_Y_INDEX); 905 put("rotation", ROTATE_INDEX); 906 } 907 }; 908 909 static int getPropertyIndex(String propertyName) { 910 if (sPropertyMap.containsKey(propertyName)) { 911 return sPropertyMap.get(propertyName); 912 } else { 913 // property not found 914 return -1; 915 } 916 } 917 918 // Temp array to store transform values obtained from native. 919 private float[] mTransform; 920 ///////////////////////////////////////////////////// 921 // Variables below need to be copied (deep copy if applicable) for mutation. 922 private final ArrayList<VObject> mChildren = new ArrayList<>(); 923 private boolean mIsStateful; 924 925 // mLocalMatrix is updated based on the update of transformation information, 926 // either parsed from the XML or by animation. 927 private @Config int mChangingConfigurations; 928 private int[] mThemeAttrs; 929 private String mGroupName = null; 930 931 // The native object will be created in the constructor and will be destroyed in native 932 // when the neither java nor native has ref to the tree. This pointer should be valid 933 // throughout this VGroup Java object's life. 934 private final long mNativePtr; 935 public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) { 936 937 mIsStateful = copy.mIsStateful; 938 mThemeAttrs = copy.mThemeAttrs; 939 mGroupName = copy.mGroupName; 940 mChangingConfigurations = copy.mChangingConfigurations; 941 if (mGroupName != null) { 942 targetsMap.put(mGroupName, this); 943 } 944 mNativePtr = nCreateGroup(copy.mNativePtr); 945 946 final ArrayList<VObject> children = copy.mChildren; 947 for (int i = 0; i < children.size(); i++) { 948 final VObject copyChild = children.get(i); 949 if (copyChild instanceof VGroup) { 950 final VGroup copyGroup = (VGroup) copyChild; 951 addChild(new VGroup(copyGroup, targetsMap)); 952 } else { 953 final VPath newPath; 954 if (copyChild instanceof VFullPath) { 955 newPath = new VFullPath((VFullPath) copyChild); 956 } else if (copyChild instanceof VClipPath) { 957 newPath = new VClipPath((VClipPath) copyChild); 958 } else { 959 throw new IllegalStateException("Unknown object in the tree!"); 960 } 961 addChild(newPath); 962 if (newPath.mPathName != null) { 963 targetsMap.put(newPath.mPathName, newPath); 964 } 965 } 966 } 967 } 968 969 public VGroup() { 970 mNativePtr = nCreateGroup(); 971 } 972 973 public String getGroupName() { 974 return mGroupName; 975 } 976 977 public void addChild(VObject child) { 978 nAddChild(mNativePtr, child.getNativePtr()); 979 mChildren.add(child); 980 981 mIsStateful |= child.isStateful(); 982 } 983 984 @Override 985 public long getNativePtr() { 986 return mNativePtr; 987 } 988 989 @Override 990 public void inflate(Resources res, AttributeSet attrs, Theme theme) { 991 final TypedArray a = obtainAttributes(res, theme, attrs, 992 R.styleable.VectorDrawableGroup); 993 updateStateFromTypedArray(a); 994 a.recycle(); 995 } 996 997 void updateStateFromTypedArray(TypedArray a) { 998 // Account for any configuration changes. 999 mChangingConfigurations |= a.getChangingConfigurations(); 1000 1001 // Extract the theme attributes, if any. 1002 mThemeAttrs = a.extractThemeAttrs(); 1003 if (mTransform == null) { 1004 // Lazy initialization: If the group is created through copy constructor, this may 1005 // never get called. 1006 mTransform = new float[TRANSFORM_PROPERTY_COUNT]; 1007 } 1008 boolean success = nGetGroupProperties(mNativePtr, mTransform, TRANSFORM_PROPERTY_COUNT); 1009 if (!success) { 1010 throw new RuntimeException("Error: inconsistent property count"); 1011 } 1012 float rotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, 1013 mTransform[ROTATE_INDEX]); 1014 float pivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, 1015 mTransform[PIVOT_X_INDEX]); 1016 float pivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, 1017 mTransform[PIVOT_Y_INDEX]); 1018 float scaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, 1019 mTransform[SCALE_X_INDEX]); 1020 float scaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, 1021 mTransform[SCALE_Y_INDEX]); 1022 float translateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, 1023 mTransform[TRANSLATE_X_INDEX]); 1024 float translateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, 1025 mTransform[TRANSLATE_Y_INDEX]); 1026 1027 final String groupName = a.getString(R.styleable.VectorDrawableGroup_name); 1028 if (groupName != null) { 1029 mGroupName = groupName; 1030 nSetName(mNativePtr, mGroupName); 1031 } 1032 nUpdateGroupProperties(mNativePtr, rotate, pivotX, pivotY, scaleX, scaleY, 1033 translateX, translateY); 1034 } 1035 1036 @Override 1037 public boolean onStateChange(int[] stateSet) { 1038 boolean changed = false; 1039 1040 final ArrayList<VObject> children = mChildren; 1041 for (int i = 0, count = children.size(); i < count; i++) { 1042 final VObject child = children.get(i); 1043 if (child.isStateful()) { 1044 changed |= child.onStateChange(stateSet); 1045 } 1046 } 1047 1048 return changed; 1049 } 1050 1051 @Override 1052 public boolean isStateful() { 1053 return mIsStateful; 1054 } 1055 1056 @Override 1057 public boolean canApplyTheme() { 1058 if (mThemeAttrs != null) { 1059 return true; 1060 } 1061 1062 final ArrayList<VObject> children = mChildren; 1063 for (int i = 0, count = children.size(); i < count; i++) { 1064 final VObject child = children.get(i); 1065 if (child.canApplyTheme()) { 1066 return true; 1067 } 1068 } 1069 1070 return false; 1071 } 1072 1073 @Override 1074 public void applyTheme(Theme t) { 1075 if (mThemeAttrs != null) { 1076 final TypedArray a = t.resolveAttributes(mThemeAttrs, 1077 R.styleable.VectorDrawableGroup); 1078 updateStateFromTypedArray(a); 1079 a.recycle(); 1080 } 1081 1082 final ArrayList<VObject> children = mChildren; 1083 for (int i = 0, count = children.size(); i < count; i++) { 1084 final VObject child = children.get(i); 1085 if (child.canApplyTheme()) { 1086 child.applyTheme(t); 1087 1088 // Applying a theme may have made the child stateful. 1089 mIsStateful |= child.isStateful(); 1090 } 1091 } 1092 } 1093 1094 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 1095 @SuppressWarnings("unused") 1096 public float getRotation() { 1097 return nGetRotation(mNativePtr); 1098 } 1099 1100 @SuppressWarnings("unused") 1101 public void setRotation(float rotation) { 1102 nSetRotation(mNativePtr, rotation); 1103 } 1104 1105 @SuppressWarnings("unused") 1106 public float getPivotX() { 1107 return nGetPivotX(mNativePtr); 1108 } 1109 1110 @SuppressWarnings("unused") 1111 public void setPivotX(float pivotX) { 1112 nSetPivotX(mNativePtr, pivotX); 1113 } 1114 1115 @SuppressWarnings("unused") 1116 public float getPivotY() { 1117 return nGetPivotY(mNativePtr); 1118 } 1119 1120 @SuppressWarnings("unused") 1121 public void setPivotY(float pivotY) { 1122 nSetPivotY(mNativePtr, pivotY); 1123 } 1124 1125 @SuppressWarnings("unused") 1126 public float getScaleX() { 1127 return nGetScaleX(mNativePtr); 1128 } 1129 1130 @SuppressWarnings("unused") 1131 public void setScaleX(float scaleX) { 1132 nSetScaleX(mNativePtr, scaleX); 1133 } 1134 1135 @SuppressWarnings("unused") 1136 public float getScaleY() { 1137 return nGetScaleY(mNativePtr); 1138 } 1139 1140 @SuppressWarnings("unused") 1141 public void setScaleY(float scaleY) { 1142 nSetScaleY(mNativePtr, scaleY); 1143 } 1144 1145 @SuppressWarnings("unused") 1146 public float getTranslateX() { 1147 return nGetTranslateX(mNativePtr); 1148 } 1149 1150 @SuppressWarnings("unused") 1151 public void setTranslateX(float translateX) { 1152 nSetTranslateX(mNativePtr, translateX); 1153 } 1154 1155 @SuppressWarnings("unused") 1156 public float getTranslateY() { 1157 return nGetTranslateY(mNativePtr); 1158 } 1159 1160 @SuppressWarnings("unused") 1161 public void setTranslateY(float translateY) { 1162 nSetTranslateY(mNativePtr, translateY); 1163 } 1164 } 1165 1166 /** 1167 * Common Path information for clip path and normal path. 1168 */ 1169 static abstract class VPath implements VObject { 1170 protected PathParser.PathData mPathData = null; 1171 1172 String mPathName; 1173 @Config int mChangingConfigurations; 1174 1175 public VPath() { 1176 // Empty constructor. 1177 } 1178 1179 public VPath(VPath copy) { 1180 mPathName = copy.mPathName; 1181 mChangingConfigurations = copy.mChangingConfigurations; 1182 mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData); 1183 } 1184 1185 public String getPathName() { 1186 return mPathName; 1187 } 1188 1189 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 1190 @SuppressWarnings("unused") 1191 public PathParser.PathData getPathData() { 1192 return mPathData; 1193 } 1194 1195 // TODO: Move the PathEvaluator and this setter and the getter above into native. 1196 @SuppressWarnings("unused") 1197 public void setPathData(PathParser.PathData pathData) { 1198 mPathData.setPathData(pathData); 1199 nSetPathData(getNativePtr(), mPathData.getNativePtr()); 1200 } 1201 } 1202 1203 /** 1204 * Clip path, which only has name and pathData. 1205 */ 1206 private static class VClipPath extends VPath { 1207 private final long mNativePtr; 1208 1209 public VClipPath() { 1210 mNativePtr = nCreateClipPath(); 1211 } 1212 1213 public VClipPath(VClipPath copy) { 1214 super(copy); 1215 mNativePtr = nCreateClipPath(copy.mNativePtr); 1216 } 1217 1218 @Override 1219 public long getNativePtr() { 1220 return mNativePtr; 1221 } 1222 1223 @Override 1224 public void inflate(Resources r, AttributeSet attrs, Theme theme) { 1225 final TypedArray a = obtainAttributes(r, theme, attrs, 1226 R.styleable.VectorDrawableClipPath); 1227 updateStateFromTypedArray(a); 1228 a.recycle(); 1229 } 1230 1231 @Override 1232 public boolean canApplyTheme() { 1233 return false; 1234 } 1235 1236 @Override 1237 public void applyTheme(Theme theme) { 1238 // No-op. 1239 } 1240 1241 @Override 1242 public boolean onStateChange(int[] stateSet) { 1243 return false; 1244 } 1245 1246 @Override 1247 public boolean isStateful() { 1248 return false; 1249 } 1250 1251 private void updateStateFromTypedArray(TypedArray a) { 1252 // Account for any configuration changes. 1253 mChangingConfigurations |= a.getChangingConfigurations(); 1254 1255 final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name); 1256 if (pathName != null) { 1257 mPathName = pathName; 1258 nSetName(mNativePtr, mPathName); 1259 } 1260 1261 final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData); 1262 if (pathDataString != null) { 1263 mPathData = new PathParser.PathData(pathDataString); 1264 nSetPathString(mNativePtr, pathDataString, pathDataString.length()); 1265 } 1266 } 1267 } 1268 1269 /** 1270 * Normal path, which contains all the fill / paint information. 1271 */ 1272 static class VFullPath extends VPath { 1273 private static final int STROKE_WIDTH_INDEX = 0; 1274 private static final int STROKE_COLOR_INDEX = 1; 1275 private static final int STROKE_ALPHA_INDEX = 2; 1276 private static final int FILL_COLOR_INDEX = 3; 1277 private static final int FILL_ALPHA_INDEX = 4; 1278 private static final int TRIM_PATH_START_INDEX = 5; 1279 private static final int TRIM_PATH_END_INDEX = 6; 1280 private static final int TRIM_PATH_OFFSET_INDEX = 7; 1281 private static final int STROKE_LINE_CAP_INDEX = 8; 1282 private static final int STROKE_LINE_JOIN_INDEX = 9; 1283 private static final int STROKE_MITER_LIMIT_INDEX = 10; 1284 private static final int FILL_TYPE_INDEX = 11; 1285 private static final int TOTAL_PROPERTY_COUNT = 12; 1286 1287 // Property map for animatable attributes. 1288 private final static HashMap<String, Integer> sPropertyMap 1289 = new HashMap<String, Integer> () { 1290 { 1291 put("strokeWidth", STROKE_WIDTH_INDEX); 1292 put("strokeColor", STROKE_COLOR_INDEX); 1293 put("strokeAlpha", STROKE_ALPHA_INDEX); 1294 put("fillColor", FILL_COLOR_INDEX); 1295 put("fillAlpha", FILL_ALPHA_INDEX); 1296 put("trimPathStart", TRIM_PATH_START_INDEX); 1297 put("trimPathEnd", TRIM_PATH_END_INDEX); 1298 put("trimPathOffset", TRIM_PATH_OFFSET_INDEX); 1299 } 1300 }; 1301 1302 // Temp array to store property data obtained from native getter. 1303 private byte[] mPropertyData; 1304 ///////////////////////////////////////////////////// 1305 // Variables below need to be copied (deep copy if applicable) for mutation. 1306 private int[] mThemeAttrs; 1307 1308 ComplexColor mStrokeColors = null; 1309 ComplexColor mFillColors = null; 1310 private final long mNativePtr; 1311 1312 public VFullPath() { 1313 mNativePtr = nCreateFullPath(); 1314 } 1315 1316 public VFullPath(VFullPath copy) { 1317 super(copy); 1318 mNativePtr = nCreateFullPath(copy.mNativePtr); 1319 mThemeAttrs = copy.mThemeAttrs; 1320 mStrokeColors = copy.mStrokeColors; 1321 mFillColors = copy.mFillColors; 1322 } 1323 1324 int getPropertyIndex(String propertyName) { 1325 if (!sPropertyMap.containsKey(propertyName)) { 1326 return -1; 1327 } else { 1328 return sPropertyMap.get(propertyName); 1329 } 1330 } 1331 1332 @Override 1333 public boolean onStateChange(int[] stateSet) { 1334 boolean changed = false; 1335 1336 if (mStrokeColors != null && mStrokeColors instanceof ColorStateList) { 1337 final int oldStrokeColor = getStrokeColor(); 1338 final int newStrokeColor = 1339 ((ColorStateList) mStrokeColors).getColorForState(stateSet, oldStrokeColor); 1340 changed |= oldStrokeColor != newStrokeColor; 1341 if (oldStrokeColor != newStrokeColor) { 1342 nSetStrokeColor(mNativePtr, newStrokeColor); 1343 } 1344 } 1345 1346 if (mFillColors != null && mFillColors instanceof ColorStateList) { 1347 final int oldFillColor = getFillColor(); 1348 final int newFillColor = ((ColorStateList) mFillColors).getColorForState(stateSet, oldFillColor); 1349 changed |= oldFillColor != newFillColor; 1350 if (oldFillColor != newFillColor) { 1351 nSetFillColor(mNativePtr, newFillColor); 1352 } 1353 } 1354 1355 return changed; 1356 } 1357 1358 @Override 1359 public boolean isStateful() { 1360 return mStrokeColors != null || mFillColors != null; 1361 } 1362 1363 @Override 1364 public long getNativePtr() { 1365 return mNativePtr; 1366 } 1367 1368 @Override 1369 public void inflate(Resources r, AttributeSet attrs, Theme theme) { 1370 final TypedArray a = obtainAttributes(r, theme, attrs, 1371 R.styleable.VectorDrawablePath); 1372 updateStateFromTypedArray(a); 1373 a.recycle(); 1374 } 1375 1376 private void updateStateFromTypedArray(TypedArray a) { 1377 int byteCount = TOTAL_PROPERTY_COUNT * 4; 1378 if (mPropertyData == null) { 1379 // Lazy initialization: If the path is created through copy constructor, this may 1380 // never get called. 1381 mPropertyData = new byte[byteCount]; 1382 } 1383 // The bulk getters/setters of property data (e.g. stroke width, color, etc) allows us 1384 // to pull current values from native and store modifications with only two methods, 1385 // minimizing JNI overhead. 1386 boolean success = nGetFullPathProperties(mNativePtr, mPropertyData, byteCount); 1387 if (!success) { 1388 throw new RuntimeException("Error: inconsistent property count"); 1389 } 1390 1391 ByteBuffer properties = ByteBuffer.wrap(mPropertyData); 1392 properties.order(ByteOrder.nativeOrder()); 1393 float strokeWidth = properties.getFloat(STROKE_WIDTH_INDEX * 4); 1394 int strokeColor = properties.getInt(STROKE_COLOR_INDEX * 4); 1395 float strokeAlpha = properties.getFloat(STROKE_ALPHA_INDEX * 4); 1396 int fillColor = properties.getInt(FILL_COLOR_INDEX * 4); 1397 float fillAlpha = properties.getFloat(FILL_ALPHA_INDEX * 4); 1398 float trimPathStart = properties.getFloat(TRIM_PATH_START_INDEX * 4); 1399 float trimPathEnd = properties.getFloat(TRIM_PATH_END_INDEX * 4); 1400 float trimPathOffset = properties.getFloat(TRIM_PATH_OFFSET_INDEX * 4); 1401 int strokeLineCap = properties.getInt(STROKE_LINE_CAP_INDEX * 4); 1402 int strokeLineJoin = properties.getInt(STROKE_LINE_JOIN_INDEX * 4); 1403 float strokeMiterLimit = properties.getFloat(STROKE_MITER_LIMIT_INDEX * 4); 1404 int fillType = properties.getInt(FILL_TYPE_INDEX * 4); 1405 Shader fillGradient = null; 1406 Shader strokeGradient = null; 1407 // Account for any configuration changes. 1408 mChangingConfigurations |= a.getChangingConfigurations(); 1409 1410 // Extract the theme attributes, if any. 1411 mThemeAttrs = a.extractThemeAttrs(); 1412 1413 final String pathName = a.getString(R.styleable.VectorDrawablePath_name); 1414 if (pathName != null) { 1415 mPathName = pathName; 1416 nSetName(mNativePtr, mPathName); 1417 } 1418 1419 final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData); 1420 if (pathString != null) { 1421 mPathData = new PathParser.PathData(pathString); 1422 nSetPathString(mNativePtr, pathString, pathString.length()); 1423 } 1424 1425 final ComplexColor fillColors = a.getComplexColor( 1426 R.styleable.VectorDrawablePath_fillColor); 1427 if (fillColors != null) { 1428 // If the colors is a gradient color, or the color state list is stateful, keep the 1429 // colors information. Otherwise, discard the colors and keep the default color. 1430 if (fillColors instanceof GradientColor) { 1431 mFillColors = fillColors; 1432 fillGradient = ((GradientColor) fillColors).getShader(); 1433 } else if (fillColors.isStateful()) { 1434 mFillColors = fillColors; 1435 } else { 1436 mFillColors = null; 1437 } 1438 fillColor = fillColors.getDefaultColor(); 1439 } 1440 1441 final ComplexColor strokeColors = a.getComplexColor( 1442 R.styleable.VectorDrawablePath_strokeColor); 1443 if (strokeColors != null) { 1444 // If the colors is a gradient color, or the color state list is stateful, keep the 1445 // colors information. Otherwise, discard the colors and keep the default color. 1446 if (strokeColors instanceof GradientColor) { 1447 mStrokeColors = strokeColors; 1448 strokeGradient = ((GradientColor) strokeColors).getShader(); 1449 } else if (strokeColors.isStateful()) { 1450 mStrokeColors = strokeColors; 1451 } else { 1452 mStrokeColors = null; 1453 } 1454 strokeColor = strokeColors.getDefaultColor(); 1455 } 1456 // Update the gradient info, even if the gradiet is null. 1457 nUpdateFullPathFillGradient(mNativePtr, 1458 fillGradient != null ? fillGradient.getNativeInstance() : 0); 1459 nUpdateFullPathStrokeGradient(mNativePtr, 1460 strokeGradient != null ? strokeGradient.getNativeInstance() : 0); 1461 1462 fillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, fillAlpha); 1463 1464 strokeLineCap = a.getInt( 1465 R.styleable.VectorDrawablePath_strokeLineCap, strokeLineCap); 1466 strokeLineJoin = a.getInt( 1467 R.styleable.VectorDrawablePath_strokeLineJoin, strokeLineJoin); 1468 strokeMiterLimit = a.getFloat( 1469 R.styleable.VectorDrawablePath_strokeMiterLimit, strokeMiterLimit); 1470 strokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha, 1471 strokeAlpha); 1472 strokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, 1473 strokeWidth); 1474 trimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, 1475 trimPathEnd); 1476 trimPathOffset = a.getFloat( 1477 R.styleable.VectorDrawablePath_trimPathOffset, trimPathOffset); 1478 trimPathStart = a.getFloat( 1479 R.styleable.VectorDrawablePath_trimPathStart, trimPathStart); 1480 fillType = a.getInt(R.styleable.VectorDrawablePath_fillType, fillType); 1481 1482 nUpdateFullPathProperties(mNativePtr, strokeWidth, strokeColor, strokeAlpha, 1483 fillColor, fillAlpha, trimPathStart, trimPathEnd, trimPathOffset, 1484 strokeMiterLimit, strokeLineCap, strokeLineJoin, fillType); 1485 } 1486 1487 @Override 1488 public boolean canApplyTheme() { 1489 if (mThemeAttrs != null) { 1490 return true; 1491 } 1492 1493 boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors); 1494 boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors); 1495 if (fillCanApplyTheme || strokeCanApplyTheme) { 1496 return true; 1497 } 1498 return false; 1499 1500 } 1501 1502 @Override 1503 public void applyTheme(Theme t) { 1504 // Resolve the theme attributes directly referred by the VectorDrawable. 1505 if (mThemeAttrs != null) { 1506 final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath); 1507 updateStateFromTypedArray(a); 1508 a.recycle(); 1509 } 1510 1511 // Resolve the theme attributes in-directly referred by the VectorDrawable, for example, 1512 // fillColor can refer to a color state list which itself needs to apply theme. 1513 // And this is the reason we still want to keep partial update for the path's properties. 1514 boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors); 1515 boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors); 1516 1517 if (fillCanApplyTheme) { 1518 mFillColors = mFillColors.obtainForTheme(t); 1519 if (mFillColors instanceof GradientColor) { 1520 nUpdateFullPathFillGradient(mNativePtr, 1521 ((GradientColor) mFillColors).getShader().getNativeInstance()); 1522 } else if (mFillColors instanceof ColorStateList) { 1523 nSetFillColor(mNativePtr, mFillColors.getDefaultColor()); 1524 } 1525 } 1526 1527 if (strokeCanApplyTheme) { 1528 mStrokeColors = mStrokeColors.obtainForTheme(t); 1529 if (mStrokeColors instanceof GradientColor) { 1530 nUpdateFullPathStrokeGradient(mNativePtr, 1531 ((GradientColor) mStrokeColors).getShader().getNativeInstance()); 1532 } else if (mStrokeColors instanceof ColorStateList) { 1533 nSetStrokeColor(mNativePtr, mStrokeColors.getDefaultColor()); 1534 } 1535 } 1536 } 1537 1538 private boolean canComplexColorApplyTheme(ComplexColor complexColor) { 1539 return complexColor != null && complexColor.canApplyTheme(); 1540 } 1541 1542 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 1543 @SuppressWarnings("unused") 1544 int getStrokeColor() { 1545 return nGetStrokeColor(mNativePtr); 1546 } 1547 1548 @SuppressWarnings("unused") 1549 void setStrokeColor(int strokeColor) { 1550 mStrokeColors = null; 1551 nSetStrokeColor(mNativePtr, strokeColor); 1552 } 1553 1554 @SuppressWarnings("unused") 1555 float getStrokeWidth() { 1556 return nGetStrokeWidth(mNativePtr); 1557 } 1558 1559 @SuppressWarnings("unused") 1560 void setStrokeWidth(float strokeWidth) { 1561 nSetStrokeWidth(mNativePtr, strokeWidth); 1562 } 1563 1564 @SuppressWarnings("unused") 1565 float getStrokeAlpha() { 1566 return nGetStrokeAlpha(mNativePtr); 1567 } 1568 1569 @SuppressWarnings("unused") 1570 void setStrokeAlpha(float strokeAlpha) { 1571 nSetStrokeAlpha(mNativePtr, strokeAlpha); 1572 } 1573 1574 @SuppressWarnings("unused") 1575 int getFillColor() { 1576 return nGetFillColor(mNativePtr); 1577 } 1578 1579 @SuppressWarnings("unused") 1580 void setFillColor(int fillColor) { 1581 mFillColors = null; 1582 nSetFillColor(mNativePtr, fillColor); 1583 } 1584 1585 @SuppressWarnings("unused") 1586 float getFillAlpha() { 1587 return nGetFillAlpha(mNativePtr); 1588 } 1589 1590 @SuppressWarnings("unused") 1591 void setFillAlpha(float fillAlpha) { 1592 nSetFillAlpha(mNativePtr, fillAlpha); 1593 } 1594 1595 @SuppressWarnings("unused") 1596 float getTrimPathStart() { 1597 return nGetTrimPathStart(mNativePtr); 1598 } 1599 1600 @SuppressWarnings("unused") 1601 void setTrimPathStart(float trimPathStart) { 1602 nSetTrimPathStart(mNativePtr, trimPathStart); 1603 } 1604 1605 @SuppressWarnings("unused") 1606 float getTrimPathEnd() { 1607 return nGetTrimPathEnd(mNativePtr); 1608 } 1609 1610 @SuppressWarnings("unused") 1611 void setTrimPathEnd(float trimPathEnd) { 1612 nSetTrimPathEnd(mNativePtr, trimPathEnd); 1613 } 1614 1615 @SuppressWarnings("unused") 1616 float getTrimPathOffset() { 1617 return nGetTrimPathOffset(mNativePtr); 1618 } 1619 1620 @SuppressWarnings("unused") 1621 void setTrimPathOffset(float trimPathOffset) { 1622 nSetTrimPathOffset(mNativePtr, trimPathOffset); 1623 } 1624 } 1625 1626 interface VObject { 1627 long getNativePtr(); 1628 void inflate(Resources r, AttributeSet attrs, Theme theme); 1629 boolean canApplyTheme(); 1630 void applyTheme(Theme t); 1631 boolean onStateChange(int[] state); 1632 boolean isStateful(); 1633 } 1634 1635 private static native long nCreateRenderer(long rootGroupPtr); 1636 private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth, 1637 float viewportHeight); 1638 private static native boolean nSetRootAlpha(long rendererPtr, float alpha); 1639 private static native float nGetRootAlpha(long rendererPtr); 1640 private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching); 1641 1642 private static native void nDraw(long rendererPtr, long canvasWrapperPtr, 1643 long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache); 1644 private static native long nCreateFullPath(); 1645 private static native long nCreateFullPath(long nativeFullPathPtr); 1646 private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties, 1647 int length); 1648 1649 private static native void nUpdateFullPathProperties(long pathPtr, float strokeWidth, 1650 int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart, 1651 float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap, 1652 int strokeLineJoin, int fillType); 1653 private static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr); 1654 private static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr); 1655 1656 private static native long nCreateClipPath(); 1657 private static native long nCreateClipPath(long clipPathPtr); 1658 1659 private static native long nCreateGroup(); 1660 private static native long nCreateGroup(long groupPtr); 1661 private static native void nSetName(long nodePtr, String name); 1662 private static native boolean nGetGroupProperties(long groupPtr, float[] properties, 1663 int length); 1664 private static native void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX, 1665 float pivotY, float scaleX, float scaleY, float translateX, float translateY); 1666 1667 private static native void nAddChild(long groupPtr, long nodePtr); 1668 private static native void nSetPathString(long pathPtr, String pathString, int length); 1669 1670 /** 1671 * The setters and getters below for paths and groups are here temporarily, and will be 1672 * removed once the animation in AVD is replaced with RenderNodeAnimator, in which case the 1673 * animation will modify these properties in native. By then no JNI hopping would be necessary 1674 * for VD during animation, and these setters and getters will be obsolete. 1675 */ 1676 // Setters and getters during animation. 1677 private static native float nGetRotation(long groupPtr); 1678 private static native void nSetRotation(long groupPtr, float rotation); 1679 private static native float nGetPivotX(long groupPtr); 1680 private static native void nSetPivotX(long groupPtr, float pivotX); 1681 private static native float nGetPivotY(long groupPtr); 1682 private static native void nSetPivotY(long groupPtr, float pivotY); 1683 private static native float nGetScaleX(long groupPtr); 1684 private static native void nSetScaleX(long groupPtr, float scaleX); 1685 private static native float nGetScaleY(long groupPtr); 1686 private static native void nSetScaleY(long groupPtr, float scaleY); 1687 private static native float nGetTranslateX(long groupPtr); 1688 private static native void nSetTranslateX(long groupPtr, float translateX); 1689 private static native float nGetTranslateY(long groupPtr); 1690 private static native void nSetTranslateY(long groupPtr, float translateY); 1691 1692 // Setters and getters for VPath during animation. 1693 private static native void nSetPathData(long pathPtr, long pathDataPtr); 1694 private static native float nGetStrokeWidth(long pathPtr); 1695 private static native void nSetStrokeWidth(long pathPtr, float width); 1696 private static native int nGetStrokeColor(long pathPtr); 1697 private static native void nSetStrokeColor(long pathPtr, int strokeColor); 1698 private static native float nGetStrokeAlpha(long pathPtr); 1699 private static native void nSetStrokeAlpha(long pathPtr, float alpha); 1700 private static native int nGetFillColor(long pathPtr); 1701 private static native void nSetFillColor(long pathPtr, int fillColor); 1702 private static native float nGetFillAlpha(long pathPtr); 1703 private static native void nSetFillAlpha(long pathPtr, float fillAlpha); 1704 private static native float nGetTrimPathStart(long pathPtr); 1705 private static native void nSetTrimPathStart(long pathPtr, float trimPathStart); 1706 private static native float nGetTrimPathEnd(long pathPtr); 1707 private static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd); 1708 private static native float nGetTrimPathOffset(long pathPtr); 1709 private static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset); 1710} 1711