1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package android.graphics.drawable; 16 17 import android.annotation.NonNull; 18 import android.annotation.Nullable; 19 import android.content.pm.ActivityInfo.Config; 20 import android.content.res.ColorStateList; 21 import android.content.res.ComplexColor; 22 import android.content.res.GradientColor; 23 import android.content.res.Resources; 24 import android.content.res.Resources.Theme; 25 import android.content.res.TypedArray; 26 import android.graphics.Canvas; 27 import android.graphics.ColorFilter; 28 import android.graphics.Insets; 29 import android.graphics.PixelFormat; 30 import android.graphics.PorterDuff.Mode; 31 import android.graphics.PorterDuffColorFilter; 32 import android.graphics.Rect; 33 import android.graphics.Shader; 34 import android.os.Trace; 35 import android.util.ArrayMap; 36 import android.util.AttributeSet; 37 import android.util.DisplayMetrics; 38 import android.util.FloatProperty; 39 import android.util.IntProperty; 40 import android.util.LayoutDirection; 41 import android.util.Log; 42 import android.util.PathParser; 43 import android.util.Property; 44 import android.util.Xml; 45 46 import com.android.internal.R; 47 import com.android.internal.util.VirtualRefBasePtr; 48 49 import org.xmlpull.v1.XmlPullParser; 50 import org.xmlpull.v1.XmlPullParserException; 51 52 import java.io.IOException; 53 import java.nio.ByteBuffer; 54 import java.nio.ByteOrder; 55 import java.util.ArrayList; 56 import java.util.HashMap; 57 import java.util.Stack; 58 59 import dalvik.annotation.optimization.FastNative; 60 import dalvik.system.VMRuntime; 61 62 /** 63 * This lets you create a drawable based on an XML vector graphic. 64 * <p/> 65 * <strong>Note:</strong> To optimize for the re-drawing performance, one bitmap cache is created 66 * for each VectorDrawable. Therefore, referring to the same VectorDrawable means sharing the same 67 * bitmap cache. If these references don't agree upon on the same size, the bitmap will be recreated 68 * and redrawn every time size is changed. In other words, if a VectorDrawable is used for 69 * different sizes, it is more efficient to create multiple VectorDrawables, one for each size. 70 * <p/> 71 * VectorDrawable can be defined in an XML file with the <code><vector></code> element. 72 * <p/> 73 * The vector drawable has the following elements: 74 * <p/> 75 * <dt><code><vector></code></dt> 76 * <dl> 77 * <dd>Used to define a vector drawable 78 * <dl> 79 * <dt><code>android:name</code></dt> 80 * <dd>Defines the name of this vector drawable.</dd> 81 * <dt><code>android:width</code></dt> 82 * <dd>Used to define the intrinsic width of the drawable. 83 * This support all the dimension units, normally specified with dp.</dd> 84 * <dt><code>android:height</code></dt> 85 * <dd>Used to define the intrinsic height the drawable. 86 * This support all the dimension units, normally specified with dp.</dd> 87 * <dt><code>android:viewportWidth</code></dt> 88 * <dd>Used to define the width of the viewport space. Viewport is basically 89 * the virtual canvas where the paths are drawn on.</dd> 90 * <dt><code>android:viewportHeight</code></dt> 91 * <dd>Used to define the height of the viewport space. Viewport is basically 92 * the virtual canvas where the paths are drawn on.</dd> 93 * <dt><code>android:tint</code></dt> 94 * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd> 95 * <dt><code>android:tintMode</code></dt> 96 * <dd>The Porter-Duff blending mode for the tint color. Default is src_in.</dd> 97 * <dt><code>android:autoMirrored</code></dt> 98 * <dd>Indicates if the drawable needs to be mirrored when its layout direction is 99 * RTL (right-to-left). Default is false.</dd> 100 * <dt><code>android:alpha</code></dt> 101 * <dd>The opacity of this drawable. Default is 1.0.</dd> 102 * </dl></dd> 103 * </dl> 104 * 105 * <dl> 106 * <dt><code><group></code></dt> 107 * <dd>Defines a group of paths or subgroups, plus transformation information. 108 * The transformations are defined in the same coordinates as the viewport. 109 * And the transformations are applied in the order of scale, rotate then translate. 110 * <dl> 111 * <dt><code>android:name</code></dt> 112 * <dd>Defines the name of the group.</dd> 113 * <dt><code>android:rotation</code></dt> 114 * <dd>The degrees of rotation of the group. Default is 0.</dd> 115 * <dt><code>android:pivotX</code></dt> 116 * <dd>The X coordinate of the pivot for the scale and rotation of the group. 117 * This is defined in the viewport space. Default is 0.</dd> 118 * <dt><code>android:pivotY</code></dt> 119 * <dd>The Y coordinate of the pivot for the scale and rotation of the group. 120 * This is defined in the viewport space. Default is 0.</dd> 121 * <dt><code>android:scaleX</code></dt> 122 * <dd>The amount of scale on the X Coordinate. Default is 1.</dd> 123 * <dt><code>android:scaleY</code></dt> 124 * <dd>The amount of scale on the Y coordinate. Default is 1.</dd> 125 * <dt><code>android:translateX</code></dt> 126 * <dd>The amount of translation on the X coordinate. 127 * This is defined in the viewport space. Default is 0.</dd> 128 * <dt><code>android:translateY</code></dt> 129 * <dd>The amount of translation on the Y coordinate. 130 * This is defined in the viewport space. Default is 0.</dd> 131 * </dl></dd> 132 * </dl> 133 * 134 * <dl> 135 * <dt><code><path></code></dt> 136 * <dd>Defines paths to be drawn. 137 * <dl> 138 * <dt><code>android:name</code></dt> 139 * <dd>Defines the name of the path.</dd> 140 * <dt><code>android:pathData</code></dt> 141 * <dd>Defines path data using exactly same format as "d" attribute 142 * in the SVG's path data. This is defined in the viewport space.</dd> 143 * <dt><code>android:fillColor</code></dt> 144 * <dd>Specifies the color used to fill the path. May be a color or, for SDK 24+, a color state list 145 * or a gradient color (See {@link android.R.styleable#GradientColor} 146 * and {@link android.R.styleable#GradientColorItem}). 147 * If this property is animated, any value set by the animation will override the original value. 148 * No path fill is drawn if this property is not specified.</dd> 149 * <dt><code>android:strokeColor</code></dt> 150 * <dd>Specifies the color used to draw the path outline. May be a color or, for SDK 24+, a color 151 * state list or a gradient color (See {@link android.R.styleable#GradientColor} 152 * and {@link android.R.styleable#GradientColorItem}). 153 * If this property is animated, any value set by the animation will override the original value. 154 * No path outline is drawn if this property is not specified.</dd> 155 * <dt><code>android:strokeWidth</code></dt> 156 * <dd>The width a path stroke. Default is 0.</dd> 157 * <dt><code>android:strokeAlpha</code></dt> 158 * <dd>The opacity of a path stroke. Default is 1.</dd> 159 * <dt><code>android:fillAlpha</code></dt> 160 * <dd>The opacity to fill the path with. Default is 1.</dd> 161 * <dt><code>android:trimPathStart</code></dt> 162 * <dd>The fraction of the path to trim from the start, in the range from 0 to 1. Default is 0.</dd> 163 * <dt><code>android:trimPathEnd</code></dt> 164 * <dd>The fraction of the path to trim from the end, in the range from 0 to 1. Default is 1.</dd> 165 * <dt><code>android:trimPathOffset</code></dt> 166 * <dd>Shift trim region (allows showed region to include the start and end), in the range 167 * from 0 to 1. Default is 0.</dd> 168 * <dt><code>android:strokeLineCap</code></dt> 169 * <dd>Sets the linecap for a stroked path: butt, round, square. Default is butt.</dd> 170 * <dt><code>android:strokeLineJoin</code></dt> 171 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel. Default is miter.</dd> 172 * <dt><code>android:strokeMiterLimit</code></dt> 173 * <dd>Sets the Miter limit for a stroked path. Default is 4.</dd> 174 * <dt><code>android:fillType</code></dt> 175 * <dd>For SDK 24+, sets the fillType for a path. The types can be either "evenOdd" or "nonZero". They behave the 176 * same as SVG's "fill-rule" properties. Default is nonZero. For more details, see 177 * <a href="https://www.w3.org/TR/SVG/painting.html#FillRuleProperty">FillRuleProperty</a></dd> 178 * </dl></dd> 179 * 180 * </dl> 181 * 182 * <dl> 183 * <dt><code><clip-path></code></dt> 184 * <dd>Defines path to be the current clip. Note that the clip path only apply to 185 * the current group and its children. 186 * <dl> 187 * <dt><code>android:name</code></dt> 188 * <dd>Defines the name of the clip path.</dd> 189 * <dd>Animatable : No.</dd> 190 * <dt><code>android:pathData</code></dt> 191 * <dd>Defines clip path using the same format as "d" attribute 192 * in the SVG's path data.</dd> 193 * <dd>Animatable : Yes.</dd> 194 * </dl></dd> 195 * </dl> 196 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file. 197 * <pre> 198 * <vector xmlns:android="http://schemas.android.com/apk/res/android" 199 * android:height="64dp" 200 * android:width="64dp" 201 * android:viewportHeight="600" 202 * android:viewportWidth="600" > 203 * <group 204 * android:name="rotationGroup" 205 * android:pivotX="300.0" 206 * android:pivotY="300.0" 207 * android:rotation="45.0" > 208 * <path 209 * android:name="v" 210 * android:fillColor="#000000" 211 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 212 * </group> 213 * </vector> 214 * </pre> 215 * </li> 216 * <h4>Gradient support</h4> 217 * We support 3 types of gradients: {@link android.graphics.LinearGradient}, 218 * {@link android.graphics.RadialGradient}, or {@link android.graphics.SweepGradient}. 219 * <p/> 220 * And we support all of 3 types of tile modes {@link android.graphics.Shader.TileMode}: 221 * CLAMP, REPEAT, MIRROR. 222 * <p/> 223 * All of the attributes are listed in {@link android.R.styleable#GradientColor}. 224 * Note that different attributes are relevant for different types of gradient. 225 * <table border="2" align="center" cellpadding="5"> 226 * <thead> 227 * <tr> 228 * <th>LinearGradient</th> 229 * <th>RadialGradient</th> 230 * <th>SweepGradient</th> 231 * </tr> 232 * </thead> 233 * <tr> 234 * <td>startColor </td> 235 * <td>startColor</td> 236 * <td>startColor</td> 237 * </tr> 238 * <tr> 239 * <td>centerColor</td> 240 * <td>centerColor</td> 241 * <td>centerColor</td> 242 * </tr> 243 * <tr> 244 * <td>endColor</td> 245 * <td>endColor</td> 246 * <td>endColor</td> 247 * </tr> 248 * <tr> 249 * <td>type</td> 250 * <td>type</td> 251 * <td>type</td> 252 * </tr> 253 * <tr> 254 * <td>tileMode</td> 255 * <td>tileMode</td> 256 * <td>tileMode</td> 257 * </tr> 258 * <tr> 259 * <td>startX</td> 260 * <td>centerX</td> 261 * <td>centerX</td> 262 * </tr> 263 * <tr> 264 * <td>startY</td> 265 * <td>centerY</td> 266 * <td>centerY</td> 267 * </tr> 268 * <tr> 269 * <td>endX</td> 270 * <td>gradientRadius</td> 271 * <td></td> 272 * </tr> 273 * <tr> 274 * <td>endY</td> 275 * <td></td> 276 * <td></td> 277 * </tr> 278 * </table> 279 * <p/> 280 * Also note that if any color item {@link android.R.styleable#GradientColorItem} is defined, then 281 * startColor, centerColor and endColor will be ignored. 282 * <p/> 283 * See more details in {@link android.R.styleable#GradientColor} and 284 * {@link android.R.styleable#GradientColorItem}. 285 * <p/> 286 * Here is a simple example that defines a linear gradient. 287 * <pre> 288 * <gradient xmlns:android="http://schemas.android.com/apk/res/android" 289 * android:startColor="?android:attr/colorPrimary" 290 * android:endColor="?android:attr/colorControlActivated" 291 * android:centerColor="#f00" 292 * android:startX="0" 293 * android:startY="0" 294 * android:endX="100" 295 * android:endY="100" 296 * android:type="linear"> 297 * </gradient> 298 * </pre> 299 * And here is a simple example that defines a radial gradient using color items. 300 * <pre> 301 * <gradient xmlns:android="http://schemas.android.com/apk/res/android" 302 * android:centerX="300" 303 * android:centerY="300" 304 * android:gradientRadius="100" 305 * android:type="radial"> 306 * <item android:offset="0.1" android:color="#0ff"/> 307 * <item android:offset="0.4" android:color="#fff"/> 308 * <item android:offset="0.9" android:color="#ff0"/> 309 * </gradient> 310 * </pre> 311 * 312 */ 313 314 public class VectorDrawable extends Drawable { 315 private static final String LOGTAG = VectorDrawable.class.getSimpleName(); 316 317 private static final String SHAPE_CLIP_PATH = "clip-path"; 318 private static final String SHAPE_GROUP = "group"; 319 private static final String SHAPE_PATH = "path"; 320 private static final String SHAPE_VECTOR = "vector"; 321 322 private VectorDrawableState mVectorState; 323 324 private PorterDuffColorFilter mTintFilter; 325 private ColorFilter mColorFilter; 326 327 private boolean mMutated; 328 329 /** The density of the display on which this drawable will be rendered. */ 330 private int mTargetDensity; 331 332 // Given the virtual display setup, the dpi can be different than the inflation's dpi. 333 // Therefore, we need to scale the values we got from the getDimension*(). 334 private int mDpiScaledWidth = 0; 335 private int mDpiScaledHeight = 0; 336 private Insets mDpiScaledInsets = Insets.NONE; 337 338 /** Whether DPI-scaled width, height, and insets need to be updated. */ 339 private boolean mDpiScaledDirty = true; 340 341 // Temp variable, only for saving "new" operation at the draw() time. 342 private final Rect mTmpBounds = new Rect(); 343 344 public VectorDrawable() { 345 this(new VectorDrawableState(null), null); 346 } 347 348 /** 349 * The one constructor to rule them all. This is called by all public 350 * constructors to set the state and initialize local properties. 351 */ 352 private VectorDrawable(@NonNull VectorDrawableState state, @Nullable Resources res) { 353 mVectorState = state; 354 updateLocalState(res); 355 } 356 357 /** 358 * Initializes local dynamic properties from state. This should be called 359 * after significant state changes, e.g. from the One True Constructor and 360 * after inflating or applying a theme. 361 * 362 * @param res resources of the context in which the drawable will be 363 * displayed, or {@code null} to use the constant state defaults 364 */ 365 private void updateLocalState(Resources res) { 366 final int density = Drawable.resolveDensity(res, mVectorState.mDensity); 367 if (mTargetDensity != density) { 368 mTargetDensity = density; 369 mDpiScaledDirty = true; 370 } 371 372 mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode); 373 } 374 375 @Override 376 public Drawable mutate() { 377 if (!mMutated && super.mutate() == this) { 378 mVectorState = new VectorDrawableState(mVectorState); 379 mMutated = true; 380 } 381 return this; 382 } 383 384 /** 385 * @hide 386 */ 387 public void clearMutated() { 388 super.clearMutated(); 389 mMutated = false; 390 } 391 392 Object getTargetByName(String name) { 393 return mVectorState.mVGTargetsMap.get(name); 394 } 395 396 @Override 397 public ConstantState getConstantState() { 398 mVectorState.mChangingConfigurations = getChangingConfigurations(); 399 return mVectorState; 400 } 401 402 @Override 403 public void draw(Canvas canvas) { 404 // We will offset the bounds for drawBitmap, so copyBounds() here instead 405 // of getBounds(). 406 copyBounds(mTmpBounds); 407 if (mTmpBounds.width() <= 0 || mTmpBounds.height() <= 0) { 408 // Nothing to draw 409 return; 410 } 411 412 // Color filters always override tint filters. 413 final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter); 414 final long colorFilterNativeInstance = colorFilter == null ? 0 : 415 colorFilter.getNativeInstance(); 416 boolean canReuseCache = mVectorState.canReuseCache(); 417 int pixelCount = nDraw(mVectorState.getNativeRenderer(), canvas.getNativeCanvasWrapper(), 418 colorFilterNativeInstance, mTmpBounds, needMirroring(), 419 canReuseCache); 420 if (pixelCount == 0) { 421 // Invalid canvas matrix or drawable bounds. This would not affect existing bitmap 422 // cache, if any. 423 return; 424 } 425 426 int deltaInBytes; 427 // Track different bitmap cache based whether the canvas is hw accelerated. By doing so, 428 // we don't over count bitmap cache allocation: if the input canvas is always of the same 429 // type, only one bitmap cache is allocated. 430 if (canvas.isHardwareAccelerated()) { 431 // Each pixel takes 4 bytes. 432 deltaInBytes = (pixelCount - mVectorState.mLastHWCachePixelCount) * 4; 433 mVectorState.mLastHWCachePixelCount = pixelCount; 434 } else { 435 // Each pixel takes 4 bytes. 436 deltaInBytes = (pixelCount - mVectorState.mLastSWCachePixelCount) * 4; 437 mVectorState.mLastSWCachePixelCount = pixelCount; 438 } 439 if (deltaInBytes > 0) { 440 VMRuntime.getRuntime().registerNativeAllocation(deltaInBytes); 441 } else if (deltaInBytes < 0) { 442 VMRuntime.getRuntime().registerNativeFree(-deltaInBytes); 443 } 444 } 445 446 447 @Override 448 public int getAlpha() { 449 return (int) (mVectorState.getAlpha() * 255); 450 } 451 452 @Override 453 public void setAlpha(int alpha) { 454 if (mVectorState.setAlpha(alpha / 255f)) { 455 invalidateSelf(); 456 } 457 } 458 459 @Override 460 public void setColorFilter(ColorFilter colorFilter) { 461 mColorFilter = colorFilter; 462 invalidateSelf(); 463 } 464 465 @Override 466 public ColorFilter getColorFilter() { 467 return mColorFilter; 468 } 469 470 @Override 471 public void setTintList(ColorStateList tint) { 472 final VectorDrawableState state = mVectorState; 473 if (state.mTint != tint) { 474 state.mTint = tint; 475 mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode); 476 invalidateSelf(); 477 } 478 } 479 480 @Override 481 public void setTintMode(Mode tintMode) { 482 final VectorDrawableState state = mVectorState; 483 if (state.mTintMode != tintMode) { 484 state.mTintMode = tintMode; 485 mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode); 486 invalidateSelf(); 487 } 488 } 489 490 @Override 491 public boolean isStateful() { 492 return super.isStateful() || (mVectorState != null && mVectorState.isStateful()); 493 } 494 495 /** @hide */ 496 @Override 497 public boolean hasFocusStateSpecified() { 498 return mVectorState != null && mVectorState.hasFocusStateSpecified(); 499 } 500 501 @Override 502 protected boolean onStateChange(int[] stateSet) { 503 boolean changed = false; 504 505 // When the VD is stateful, we need to mutate the drawable such that we don't share the 506 // cache bitmap with others. Such that the state change only affect this new cached bitmap. 507 if (isStateful()) { 508 mutate(); 509 } 510 final VectorDrawableState state = mVectorState; 511 if (state.onStateChange(stateSet)) { 512 changed = true; 513 state.mCacheDirty = true; 514 } 515 if (state.mTint != null && state.mTintMode != null) { 516 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 517 changed = true; 518 } 519 520 return changed; 521 } 522 523 @Override 524 public int getOpacity() { 525 // We can't tell whether the drawable is fully opaque unless we examine all the pixels, 526 // but we could tell it is transparent if the root alpha is 0. 527 return getAlpha() == 0 ? PixelFormat.TRANSPARENT : PixelFormat.TRANSLUCENT; 528 } 529 530 @Override 531 public int getIntrinsicWidth() { 532 if (mDpiScaledDirty) { 533 computeVectorSize(); 534 } 535 return mDpiScaledWidth; 536 } 537 538 @Override 539 public int getIntrinsicHeight() { 540 if (mDpiScaledDirty) { 541 computeVectorSize(); 542 } 543 return mDpiScaledHeight; 544 } 545 546 /** @hide */ 547 @Override 548 public Insets getOpticalInsets() { 549 if (mDpiScaledDirty) { 550 computeVectorSize(); 551 } 552 return mDpiScaledInsets; 553 } 554 555 /* 556 * Update local dimensions to adjust for a target density that may differ 557 * from the source density against which the constant state was loaded. 558 */ 559 void computeVectorSize() { 560 final Insets opticalInsets = mVectorState.mOpticalInsets; 561 562 final int sourceDensity = mVectorState.mDensity; 563 final int targetDensity = mTargetDensity; 564 if (targetDensity != sourceDensity) { 565 mDpiScaledWidth = Drawable.scaleFromDensity(mVectorState.mBaseWidth, sourceDensity, 566 targetDensity, true); 567 mDpiScaledHeight = Drawable.scaleFromDensity(mVectorState.mBaseHeight,sourceDensity, 568 targetDensity, true); 569 final int left = Drawable.scaleFromDensity( 570 opticalInsets.left, sourceDensity, targetDensity, false); 571 final int right = Drawable.scaleFromDensity( 572 opticalInsets.right, sourceDensity, targetDensity, false); 573 final int top = Drawable.scaleFromDensity( 574 opticalInsets.top, sourceDensity, targetDensity, false); 575 final int bottom = Drawable.scaleFromDensity( 576 opticalInsets.bottom, sourceDensity, targetDensity, false); 577 mDpiScaledInsets = Insets.of(left, top, right, bottom); 578 } else { 579 mDpiScaledWidth = mVectorState.mBaseWidth; 580 mDpiScaledHeight = mVectorState.mBaseHeight; 581 mDpiScaledInsets = opticalInsets; 582 } 583 584 mDpiScaledDirty = false; 585 } 586 587 @Override 588 public boolean canApplyTheme() { 589 return (mVectorState != null && mVectorState.canApplyTheme()) || super.canApplyTheme(); 590 } 591 592 @Override 593 public void applyTheme(Theme t) { 594 super.applyTheme(t); 595 596 final VectorDrawableState state = mVectorState; 597 if (state == null) { 598 return; 599 } 600 601 final boolean changedDensity = mVectorState.setDensity( 602 Drawable.resolveDensity(t.getResources(), 0)); 603 mDpiScaledDirty |= changedDensity; 604 605 if (state.mThemeAttrs != null) { 606 final TypedArray a = t.resolveAttributes( 607 state.mThemeAttrs, R.styleable.VectorDrawable); 608 try { 609 state.mCacheDirty = true; 610 updateStateFromTypedArray(a); 611 } catch (XmlPullParserException e) { 612 throw new RuntimeException(e); 613 } finally { 614 a.recycle(); 615 } 616 617 // May have changed size. 618 mDpiScaledDirty = true; 619 } 620 621 // Apply theme to contained color state list. 622 if (state.mTint != null && state.mTint.canApplyTheme()) { 623 state.mTint = state.mTint.obtainForTheme(t); 624 } 625 626 if (mVectorState != null && mVectorState.canApplyTheme()) { 627 mVectorState.applyTheme(t); 628 } 629 630 // Update local properties. 631 updateLocalState(t.getResources()); 632 } 633 634 /** 635 * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension. 636 * This is used to calculate the path animation accuracy. 637 * 638 * @hide 639 */ 640 public float getPixelSize() { 641 if (mVectorState == null || 642 mVectorState.mBaseWidth == 0 || 643 mVectorState.mBaseHeight == 0 || 644 mVectorState.mViewportHeight == 0 || 645 mVectorState.mViewportWidth == 0) { 646 return 1; // fall back to 1:1 pixel mapping. 647 } 648 float intrinsicWidth = mVectorState.mBaseWidth; 649 float intrinsicHeight = mVectorState.mBaseHeight; 650 float viewportWidth = mVectorState.mViewportWidth; 651 float viewportHeight = mVectorState.mViewportHeight; 652 float scaleX = viewportWidth / intrinsicWidth; 653 float scaleY = viewportHeight / intrinsicHeight; 654 return Math.min(scaleX, scaleY); 655 } 656 657 /** @hide */ 658 public static VectorDrawable create(Resources resources, int rid) { 659 try { 660 final XmlPullParser parser = resources.getXml(rid); 661 final AttributeSet attrs = Xml.asAttributeSet(parser); 662 int type; 663 while ((type=parser.next()) != XmlPullParser.START_TAG && 664 type != XmlPullParser.END_DOCUMENT) { 665 // Empty loop 666 } 667 if (type != XmlPullParser.START_TAG) { 668 throw new XmlPullParserException("No start tag found"); 669 } 670 671 final VectorDrawable drawable = new VectorDrawable(); 672 drawable.inflate(resources, parser, attrs); 673 674 return drawable; 675 } catch (XmlPullParserException e) { 676 Log.e(LOGTAG, "parser error", e); 677 } catch (IOException e) { 678 Log.e(LOGTAG, "parser error", e); 679 } 680 return null; 681 } 682 683 @Override 684 public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, 685 @NonNull AttributeSet attrs, @Nullable Theme theme) 686 throws XmlPullParserException, IOException { 687 try { 688 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "VectorDrawable#inflate"); 689 if (mVectorState.mRootGroup != null || mVectorState.mNativeTree != null) { 690 // This VD has been used to display other VD resource content, clean up. 691 if (mVectorState.mRootGroup != null) { 692 // Subtract the native allocation for all the nodes. 693 VMRuntime.getRuntime().registerNativeFree( 694 mVectorState.mRootGroup.getNativeSize()); 695 // Remove child nodes' reference to tree 696 mVectorState.mRootGroup.setTree(null); 697 } 698 mVectorState.mRootGroup = new VGroup(); 699 if (mVectorState.mNativeTree != null) { 700 // Subtract the native allocation for the tree wrapper, which contains root node 701 // as well as rendering related data. 702 VMRuntime.getRuntime().registerNativeFree(mVectorState.NATIVE_ALLOCATION_SIZE); 703 mVectorState.mNativeTree.release(); 704 } 705 mVectorState.createNativeTree(mVectorState.mRootGroup); 706 } 707 final VectorDrawableState state = mVectorState; 708 state.setDensity(Drawable.resolveDensity(r, 0)); 709 710 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable); 711 updateStateFromTypedArray(a); 712 a.recycle(); 713 714 mDpiScaledDirty = true; 715 716 state.mCacheDirty = true; 717 inflateChildElements(r, parser, attrs, theme); 718 719 state.onTreeConstructionFinished(); 720 // Update local properties. 721 updateLocalState(r); 722 } finally { 723 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 724 } 725 } 726 727 private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { 728 final VectorDrawableState state = mVectorState; 729 730 // Account for any configuration changes. 731 state.mChangingConfigurations |= a.getChangingConfigurations(); 732 733 // Extract the theme attributes, if any. 734 state.mThemeAttrs = a.extractThemeAttrs(); 735 736 final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1); 737 if (tintMode != -1) { 738 state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); 739 } 740 741 final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint); 742 if (tint != null) { 743 state.mTint = tint; 744 } 745 746 state.mAutoMirrored = a.getBoolean( 747 R.styleable.VectorDrawable_autoMirrored, state.mAutoMirrored); 748 749 float viewportWidth = a.getFloat( 750 R.styleable.VectorDrawable_viewportWidth, state.mViewportWidth); 751 float viewportHeight = a.getFloat( 752 R.styleable.VectorDrawable_viewportHeight, state.mViewportHeight); 753 state.setViewportSize(viewportWidth, viewportHeight); 754 755 if (state.mViewportWidth <= 0) { 756 throw new XmlPullParserException(a.getPositionDescription() + 757 "<vector> tag requires viewportWidth > 0"); 758 } else if (state.mViewportHeight <= 0) { 759 throw new XmlPullParserException(a.getPositionDescription() + 760 "<vector> tag requires viewportHeight > 0"); 761 } 762 763 state.mBaseWidth = a.getDimensionPixelSize( 764 R.styleable.VectorDrawable_width, state.mBaseWidth); 765 state.mBaseHeight = a.getDimensionPixelSize( 766 R.styleable.VectorDrawable_height, state.mBaseHeight); 767 768 if (state.mBaseWidth <= 0) { 769 throw new XmlPullParserException(a.getPositionDescription() + 770 "<vector> tag requires width > 0"); 771 } else if (state.mBaseHeight <= 0) { 772 throw new XmlPullParserException(a.getPositionDescription() + 773 "<vector> tag requires height > 0"); 774 } 775 776 final int insetLeft = a.getDimensionPixelOffset( 777 R.styleable.VectorDrawable_opticalInsetLeft, state.mOpticalInsets.left); 778 final int insetTop = a.getDimensionPixelOffset( 779 R.styleable.VectorDrawable_opticalInsetTop, state.mOpticalInsets.top); 780 final int insetRight = a.getDimensionPixelOffset( 781 R.styleable.VectorDrawable_opticalInsetRight, state.mOpticalInsets.right); 782 final int insetBottom = a.getDimensionPixelOffset( 783 R.styleable.VectorDrawable_opticalInsetBottom, state.mOpticalInsets.bottom); 784 state.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom); 785 786 final float alphaInFloat = a.getFloat( 787 R.styleable.VectorDrawable_alpha, state.getAlpha()); 788 state.setAlpha(alphaInFloat); 789 790 final String name = a.getString(R.styleable.VectorDrawable_name); 791 if (name != null) { 792 state.mRootName = name; 793 state.mVGTargetsMap.put(name, state); 794 } 795 } 796 797 private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs, 798 Theme theme) throws XmlPullParserException, IOException { 799 final VectorDrawableState state = mVectorState; 800 boolean noPathTag = true; 801 802 // Use a stack to help to build the group tree. 803 // The top of the stack is always the current group. 804 final Stack<VGroup> groupStack = new Stack<VGroup>(); 805 groupStack.push(state.mRootGroup); 806 807 int eventType = parser.getEventType(); 808 final int innerDepth = parser.getDepth() + 1; 809 810 // Parse everything until the end of the vector element. 811 while (eventType != XmlPullParser.END_DOCUMENT 812 && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) { 813 if (eventType == XmlPullParser.START_TAG) { 814 final String tagName = parser.getName(); 815 final VGroup currentGroup = groupStack.peek(); 816 817 if (SHAPE_PATH.equals(tagName)) { 818 final VFullPath path = new VFullPath(); 819 path.inflate(res, attrs, theme); 820 currentGroup.addChild(path); 821 if (path.getPathName() != null) { 822 state.mVGTargetsMap.put(path.getPathName(), path); 823 } 824 noPathTag = false; 825 state.mChangingConfigurations |= path.mChangingConfigurations; 826 } else if (SHAPE_CLIP_PATH.equals(tagName)) { 827 final VClipPath path = new VClipPath(); 828 path.inflate(res, attrs, theme); 829 currentGroup.addChild(path); 830 if (path.getPathName() != null) { 831 state.mVGTargetsMap.put(path.getPathName(), path); 832 } 833 state.mChangingConfigurations |= path.mChangingConfigurations; 834 } else if (SHAPE_GROUP.equals(tagName)) { 835 VGroup newChildGroup = new VGroup(); 836 newChildGroup.inflate(res, attrs, theme); 837 currentGroup.addChild(newChildGroup); 838 groupStack.push(newChildGroup); 839 if (newChildGroup.getGroupName() != null) { 840 state.mVGTargetsMap.put(newChildGroup.getGroupName(), 841 newChildGroup); 842 } 843 state.mChangingConfigurations |= newChildGroup.mChangingConfigurations; 844 } 845 } else if (eventType == XmlPullParser.END_TAG) { 846 final String tagName = parser.getName(); 847 if (SHAPE_GROUP.equals(tagName)) { 848 groupStack.pop(); 849 } 850 } 851 eventType = parser.next(); 852 } 853 854 if (noPathTag) { 855 final StringBuffer tag = new StringBuffer(); 856 857 if (tag.length() > 0) { 858 tag.append(" or "); 859 } 860 tag.append(SHAPE_PATH); 861 862 throw new XmlPullParserException("no " + tag + " defined"); 863 } 864 } 865 866 @Override 867 public @Config int getChangingConfigurations() { 868 return super.getChangingConfigurations() | mVectorState.getChangingConfigurations(); 869 } 870 871 void setAllowCaching(boolean allowCaching) { 872 nSetAllowCaching(mVectorState.getNativeRenderer(), allowCaching); 873 } 874 875 private boolean needMirroring() { 876 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 877 } 878 879 @Override 880 public void setAutoMirrored(boolean mirrored) { 881 if (mVectorState.mAutoMirrored != mirrored) { 882 mVectorState.mAutoMirrored = mirrored; 883 invalidateSelf(); 884 } 885 } 886 887 @Override 888 public boolean isAutoMirrored() { 889 return mVectorState.mAutoMirrored; 890 } 891 892 /** 893 * @hide 894 */ 895 public long getNativeTree() { 896 return mVectorState.getNativeRenderer(); 897 } 898 899 /** 900 * @hide 901 */ 902 public void setAntiAlias(boolean aa) { 903 nSetAntiAlias(mVectorState.mNativeTree.get(), aa); 904 } 905 906 static class VectorDrawableState extends ConstantState { 907 // Variables below need to be copied (deep copy if applicable) for mutation. 908 int[] mThemeAttrs; 909 @Config int mChangingConfigurations; 910 ColorStateList mTint = null; 911 Mode mTintMode = DEFAULT_TINT_MODE; 912 boolean mAutoMirrored; 913 914 int mBaseWidth = 0; 915 int mBaseHeight = 0; 916 float mViewportWidth = 0; 917 float mViewportHeight = 0; 918 Insets mOpticalInsets = Insets.NONE; 919 String mRootName = null; 920 VGroup mRootGroup; 921 VirtualRefBasePtr mNativeTree = null; 922 923 int mDensity = DisplayMetrics.DENSITY_DEFAULT; 924 final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>(); 925 926 // Fields for cache 927 int[] mCachedThemeAttrs; 928 ColorStateList mCachedTint; 929 Mode mCachedTintMode; 930 boolean mCachedAutoMirrored; 931 boolean mCacheDirty; 932 933 // Since sw canvas and hw canvas uses different bitmap caches, we track the allocation of 934 // these bitmaps separately. 935 int mLastSWCachePixelCount = 0; 936 int mLastHWCachePixelCount = 0; 937 938 final static Property<VectorDrawableState, Float> ALPHA = 939 new FloatProperty<VectorDrawableState>("alpha") { 940 @Override 941 public void setValue(VectorDrawableState state, float value) { 942 state.setAlpha(value); 943 } 944 945 @Override 946 public Float get(VectorDrawableState state) { 947 return state.getAlpha(); 948 } 949 }; 950 951 Property getProperty(String propertyName) { 952 if (ALPHA.getName().equals(propertyName)) { 953 return ALPHA; 954 } 955 return null; 956 } 957 958 // This tracks the total native allocation for all the nodes. 959 private int mAllocationOfAllNodes = 0; 960 961 private static final int NATIVE_ALLOCATION_SIZE = 316; 962 963 // If copy is not null, deep copy the given VectorDrawableState. Otherwise, create a 964 // native vector drawable tree with an empty root group. 965 public VectorDrawableState(VectorDrawableState copy) { 966 if (copy != null) { 967 mThemeAttrs = copy.mThemeAttrs; 968 mChangingConfigurations = copy.mChangingConfigurations; 969 mTint = copy.mTint; 970 mTintMode = copy.mTintMode; 971 mAutoMirrored = copy.mAutoMirrored; 972 mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap); 973 createNativeTreeFromCopy(copy, mRootGroup); 974 975 mBaseWidth = copy.mBaseWidth; 976 mBaseHeight = copy.mBaseHeight; 977 setViewportSize(copy.mViewportWidth, copy.mViewportHeight); 978 mOpticalInsets = copy.mOpticalInsets; 979 980 mRootName = copy.mRootName; 981 mDensity = copy.mDensity; 982 if (copy.mRootName != null) { 983 mVGTargetsMap.put(copy.mRootName, this); 984 } 985 } else { 986 mRootGroup = new VGroup(); 987 createNativeTree(mRootGroup); 988 } 989 onTreeConstructionFinished(); 990 } 991 992 private void createNativeTree(VGroup rootGroup) { 993 mNativeTree = new VirtualRefBasePtr(nCreateTree(rootGroup.mNativePtr)); 994 // Register tree size 995 VMRuntime.getRuntime().registerNativeAllocation(NATIVE_ALLOCATION_SIZE); 996 } 997 998 // Create a new native tree with the given root group, and copy the properties from the 999 // given VectorDrawableState's native tree. 1000 private void createNativeTreeFromCopy(VectorDrawableState copy, VGroup rootGroup) { 1001 mNativeTree = new VirtualRefBasePtr(nCreateTreeFromCopy( 1002 copy.mNativeTree.get(), rootGroup.mNativePtr)); 1003 // Register tree size 1004 VMRuntime.getRuntime().registerNativeAllocation(NATIVE_ALLOCATION_SIZE); 1005 } 1006 1007 // This should be called every time after a new RootGroup and all its subtrees are created 1008 // (i.e. in constructors of VectorDrawableState and in inflate). 1009 void onTreeConstructionFinished() { 1010 mRootGroup.setTree(mNativeTree); 1011 mAllocationOfAllNodes = mRootGroup.getNativeSize(); 1012 VMRuntime.getRuntime().registerNativeAllocation(mAllocationOfAllNodes); 1013 } 1014 1015 long getNativeRenderer() { 1016 if (mNativeTree == null) { 1017 return 0; 1018 } 1019 return mNativeTree.get(); 1020 } 1021 1022 public boolean canReuseCache() { 1023 if (!mCacheDirty 1024 && mCachedThemeAttrs == mThemeAttrs 1025 && mCachedTint == mTint 1026 && mCachedTintMode == mTintMode 1027 && mCachedAutoMirrored == mAutoMirrored) { 1028 return true; 1029 } 1030 updateCacheStates(); 1031 return false; 1032 } 1033 1034 public void updateCacheStates() { 1035 // Use shallow copy here and shallow comparison in canReuseCache(), 1036 // likely hit cache miss more, but practically not much difference. 1037 mCachedThemeAttrs = mThemeAttrs; 1038 mCachedTint = mTint; 1039 mCachedTintMode = mTintMode; 1040 mCachedAutoMirrored = mAutoMirrored; 1041 mCacheDirty = false; 1042 } 1043 1044 public void applyTheme(Theme t) { 1045 mRootGroup.applyTheme(t); 1046 } 1047 1048 @Override 1049 public boolean canApplyTheme() { 1050 return mThemeAttrs != null 1051 || (mRootGroup != null && mRootGroup.canApplyTheme()) 1052 || (mTint != null && mTint.canApplyTheme()) 1053 || super.canApplyTheme(); 1054 } 1055 1056 @Override 1057 public Drawable newDrawable() { 1058 return new VectorDrawable(this, null); 1059 } 1060 1061 @Override 1062 public Drawable newDrawable(Resources res) { 1063 return new VectorDrawable(this, res); 1064 } 1065 1066 @Override 1067 public @Config int getChangingConfigurations() { 1068 return mChangingConfigurations 1069 | (mTint != null ? mTint.getChangingConfigurations() : 0); 1070 } 1071 1072 public boolean isStateful() { 1073 return (mTint != null && mTint.isStateful()) 1074 || (mRootGroup != null && mRootGroup.isStateful()); 1075 } 1076 1077 public boolean hasFocusStateSpecified() { 1078 return mTint != null && mTint.hasFocusStateSpecified() 1079 || (mRootGroup != null && mRootGroup.hasFocusStateSpecified()); 1080 } 1081 1082 void setViewportSize(float viewportWidth, float viewportHeight) { 1083 mViewportWidth = viewportWidth; 1084 mViewportHeight = viewportHeight; 1085 nSetRendererViewportSize(getNativeRenderer(), viewportWidth, viewportHeight); 1086 } 1087 1088 public final boolean setDensity(int targetDensity) { 1089 if (mDensity != targetDensity) { 1090 final int sourceDensity = mDensity; 1091 mDensity = targetDensity; 1092 applyDensityScaling(sourceDensity, targetDensity); 1093 return true; 1094 } 1095 return false; 1096 } 1097 1098 private void applyDensityScaling(int sourceDensity, int targetDensity) { 1099 mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity, true); 1100 mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity, 1101 true); 1102 1103 final int insetLeft = Drawable.scaleFromDensity( 1104 mOpticalInsets.left, sourceDensity, targetDensity, false); 1105 final int insetTop = Drawable.scaleFromDensity( 1106 mOpticalInsets.top, sourceDensity, targetDensity, false); 1107 final int insetRight = Drawable.scaleFromDensity( 1108 mOpticalInsets.right, sourceDensity, targetDensity, false); 1109 final int insetBottom = Drawable.scaleFromDensity( 1110 mOpticalInsets.bottom, sourceDensity, targetDensity, false); 1111 mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom); 1112 } 1113 1114 public boolean onStateChange(int[] stateSet) { 1115 return mRootGroup.onStateChange(stateSet); 1116 } 1117 1118 @Override 1119 public void finalize() throws Throwable { 1120 super.finalize(); 1121 int bitmapCacheSize = mLastHWCachePixelCount * 4 + mLastSWCachePixelCount * 4; 1122 VMRuntime.getRuntime().registerNativeFree(NATIVE_ALLOCATION_SIZE 1123 + mAllocationOfAllNodes + bitmapCacheSize); 1124 } 1125 1126 /** 1127 * setAlpha() and getAlpha() are used mostly for animation purpose. Return true if alpha 1128 * has changed. 1129 */ 1130 public boolean setAlpha(float alpha) { 1131 return nSetRootAlpha(mNativeTree.get(), alpha); 1132 } 1133 1134 @SuppressWarnings("unused") 1135 public float getAlpha() { 1136 return nGetRootAlpha(mNativeTree.get()); 1137 } 1138 } 1139 1140 static class VGroup extends VObject { 1141 private static final int ROTATION_INDEX = 0; 1142 private static final int PIVOT_X_INDEX = 1; 1143 private static final int PIVOT_Y_INDEX = 2; 1144 private static final int SCALE_X_INDEX = 3; 1145 private static final int SCALE_Y_INDEX = 4; 1146 private static final int TRANSLATE_X_INDEX = 5; 1147 private static final int TRANSLATE_Y_INDEX = 6; 1148 private static final int TRANSFORM_PROPERTY_COUNT = 7; 1149 1150 private static final int NATIVE_ALLOCATION_SIZE = 100; 1151 1152 private static final HashMap<String, Integer> sPropertyIndexMap = 1153 new HashMap<String, Integer>() { 1154 { 1155 put("translateX", TRANSLATE_X_INDEX); 1156 put("translateY", TRANSLATE_Y_INDEX); 1157 put("scaleX", SCALE_X_INDEX); 1158 put("scaleY", SCALE_Y_INDEX); 1159 put("pivotX", PIVOT_X_INDEX); 1160 put("pivotY", PIVOT_Y_INDEX); 1161 put("rotation", ROTATION_INDEX); 1162 } 1163 }; 1164 1165 static int getPropertyIndex(String propertyName) { 1166 if (sPropertyIndexMap.containsKey(propertyName)) { 1167 return sPropertyIndexMap.get(propertyName); 1168 } else { 1169 // property not found 1170 return -1; 1171 } 1172 } 1173 1174 // Below are the Properties that wrap the setters to avoid reflection overhead in animations 1175 private static final Property<VGroup, Float> TRANSLATE_X = 1176 new FloatProperty<VGroup> ("translateX") { 1177 @Override 1178 public void setValue(VGroup object, float value) { 1179 object.setTranslateX(value); 1180 } 1181 1182 @Override 1183 public Float get(VGroup object) { 1184 return object.getTranslateX(); 1185 } 1186 }; 1187 1188 private static final Property<VGroup, Float> TRANSLATE_Y = 1189 new FloatProperty<VGroup> ("translateY") { 1190 @Override 1191 public void setValue(VGroup object, float value) { 1192 object.setTranslateY(value); 1193 } 1194 1195 @Override 1196 public Float get(VGroup object) { 1197 return object.getTranslateY(); 1198 } 1199 }; 1200 1201 private static final Property<VGroup, Float> SCALE_X = 1202 new FloatProperty<VGroup> ("scaleX") { 1203 @Override 1204 public void setValue(VGroup object, float value) { 1205 object.setScaleX(value); 1206 } 1207 1208 @Override 1209 public Float get(VGroup object) { 1210 return object.getScaleX(); 1211 } 1212 }; 1213 1214 private static final Property<VGroup, Float> SCALE_Y = 1215 new FloatProperty<VGroup> ("scaleY") { 1216 @Override 1217 public void setValue(VGroup object, float value) { 1218 object.setScaleY(value); 1219 } 1220 1221 @Override 1222 public Float get(VGroup object) { 1223 return object.getScaleY(); 1224 } 1225 }; 1226 1227 private static final Property<VGroup, Float> PIVOT_X = 1228 new FloatProperty<VGroup> ("pivotX") { 1229 @Override 1230 public void setValue(VGroup object, float value) { 1231 object.setPivotX(value); 1232 } 1233 1234 @Override 1235 public Float get(VGroup object) { 1236 return object.getPivotX(); 1237 } 1238 }; 1239 1240 private static final Property<VGroup, Float> PIVOT_Y = 1241 new FloatProperty<VGroup> ("pivotY") { 1242 @Override 1243 public void setValue(VGroup object, float value) { 1244 object.setPivotY(value); 1245 } 1246 1247 @Override 1248 public Float get(VGroup object) { 1249 return object.getPivotY(); 1250 } 1251 }; 1252 1253 private static final Property<VGroup, Float> ROTATION = 1254 new FloatProperty<VGroup> ("rotation") { 1255 @Override 1256 public void setValue(VGroup object, float value) { 1257 object.setRotation(value); 1258 } 1259 1260 @Override 1261 public Float get(VGroup object) { 1262 return object.getRotation(); 1263 } 1264 }; 1265 1266 private static final HashMap<String, Property> sPropertyMap = 1267 new HashMap<String, Property>() { 1268 { 1269 put("translateX", TRANSLATE_X); 1270 put("translateY", TRANSLATE_Y); 1271 put("scaleX", SCALE_X); 1272 put("scaleY", SCALE_Y); 1273 put("pivotX", PIVOT_X); 1274 put("pivotY", PIVOT_Y); 1275 put("rotation", ROTATION); 1276 } 1277 }; 1278 // Temp array to store transform values obtained from native. 1279 private float[] mTransform; 1280 ///////////////////////////////////////////////////// 1281 // Variables below need to be copied (deep copy if applicable) for mutation. 1282 private final ArrayList<VObject> mChildren = new ArrayList<>(); 1283 private boolean mIsStateful; 1284 1285 // mLocalMatrix is updated based on the update of transformation information, 1286 // either parsed from the XML or by animation. 1287 private @Config int mChangingConfigurations; 1288 private int[] mThemeAttrs; 1289 private String mGroupName = null; 1290 1291 // The native object will be created in the constructor and will be destroyed in native 1292 // when the neither java nor native has ref to the tree. This pointer should be valid 1293 // throughout this VGroup Java object's life. 1294 private final long mNativePtr; 1295 public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) { 1296 1297 mIsStateful = copy.mIsStateful; 1298 mThemeAttrs = copy.mThemeAttrs; 1299 mGroupName = copy.mGroupName; 1300 mChangingConfigurations = copy.mChangingConfigurations; 1301 if (mGroupName != null) { 1302 targetsMap.put(mGroupName, this); 1303 } 1304 mNativePtr = nCreateGroup(copy.mNativePtr); 1305 1306 final ArrayList<VObject> children = copy.mChildren; 1307 for (int i = 0; i < children.size(); i++) { 1308 final VObject copyChild = children.get(i); 1309 if (copyChild instanceof VGroup) { 1310 final VGroup copyGroup = (VGroup) copyChild; 1311 addChild(new VGroup(copyGroup, targetsMap)); 1312 } else { 1313 final VPath newPath; 1314 if (copyChild instanceof VFullPath) { 1315 newPath = new VFullPath((VFullPath) copyChild); 1316 } else if (copyChild instanceof VClipPath) { 1317 newPath = new VClipPath((VClipPath) copyChild); 1318 } else { 1319 throw new IllegalStateException("Unknown object in the tree!"); 1320 } 1321 addChild(newPath); 1322 if (newPath.mPathName != null) { 1323 targetsMap.put(newPath.mPathName, newPath); 1324 } 1325 } 1326 } 1327 } 1328 1329 public VGroup() { 1330 mNativePtr = nCreateGroup(); 1331 } 1332 1333 Property getProperty(String propertyName) { 1334 if (sPropertyMap.containsKey(propertyName)) { 1335 return sPropertyMap.get(propertyName); 1336 } else { 1337 // property not found 1338 return null; 1339 } 1340 } 1341 1342 public String getGroupName() { 1343 return mGroupName; 1344 } 1345 1346 public void addChild(VObject child) { 1347 nAddChild(mNativePtr, child.getNativePtr()); 1348 mChildren.add(child); 1349 mIsStateful |= child.isStateful(); 1350 } 1351 1352 @Override 1353 public void setTree(VirtualRefBasePtr treeRoot) { 1354 super.setTree(treeRoot); 1355 for (int i = 0; i < mChildren.size(); i++) { 1356 mChildren.get(i).setTree(treeRoot); 1357 } 1358 } 1359 1360 @Override 1361 public long getNativePtr() { 1362 return mNativePtr; 1363 } 1364 1365 @Override 1366 public void inflate(Resources res, AttributeSet attrs, Theme theme) { 1367 final TypedArray a = obtainAttributes(res, theme, attrs, 1368 R.styleable.VectorDrawableGroup); 1369 updateStateFromTypedArray(a); 1370 a.recycle(); 1371 } 1372 1373 void updateStateFromTypedArray(TypedArray a) { 1374 // Account for any configuration changes. 1375 mChangingConfigurations |= a.getChangingConfigurations(); 1376 1377 // Extract the theme attributes, if any. 1378 mThemeAttrs = a.extractThemeAttrs(); 1379 if (mTransform == null) { 1380 // Lazy initialization: If the group is created through copy constructor, this may 1381 // never get called. 1382 mTransform = new float[TRANSFORM_PROPERTY_COUNT]; 1383 } 1384 boolean success = nGetGroupProperties(mNativePtr, mTransform, TRANSFORM_PROPERTY_COUNT); 1385 if (!success) { 1386 throw new RuntimeException("Error: inconsistent property count"); 1387 } 1388 float rotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, 1389 mTransform[ROTATION_INDEX]); 1390 float pivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, 1391 mTransform[PIVOT_X_INDEX]); 1392 float pivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, 1393 mTransform[PIVOT_Y_INDEX]); 1394 float scaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, 1395 mTransform[SCALE_X_INDEX]); 1396 float scaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, 1397 mTransform[SCALE_Y_INDEX]); 1398 float translateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, 1399 mTransform[TRANSLATE_X_INDEX]); 1400 float translateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, 1401 mTransform[TRANSLATE_Y_INDEX]); 1402 1403 final String groupName = a.getString(R.styleable.VectorDrawableGroup_name); 1404 if (groupName != null) { 1405 mGroupName = groupName; 1406 nSetName(mNativePtr, mGroupName); 1407 } 1408 nUpdateGroupProperties(mNativePtr, rotate, pivotX, pivotY, scaleX, scaleY, 1409 translateX, translateY); 1410 } 1411 1412 @Override 1413 public boolean onStateChange(int[] stateSet) { 1414 boolean changed = false; 1415 1416 final ArrayList<VObject> children = mChildren; 1417 for (int i = 0, count = children.size(); i < count; i++) { 1418 final VObject child = children.get(i); 1419 if (child.isStateful()) { 1420 changed |= child.onStateChange(stateSet); 1421 } 1422 } 1423 1424 return changed; 1425 } 1426 1427 @Override 1428 public boolean isStateful() { 1429 return mIsStateful; 1430 } 1431 1432 @Override 1433 public boolean hasFocusStateSpecified() { 1434 boolean result = false; 1435 1436 final ArrayList<VObject> children = mChildren; 1437 for (int i = 0, count = children.size(); i < count; i++) { 1438 final VObject child = children.get(i); 1439 if (child.isStateful()) { 1440 result |= child.hasFocusStateSpecified(); 1441 } 1442 } 1443 1444 return result; 1445 } 1446 1447 @Override 1448 int getNativeSize() { 1449 // Return the native allocation needed for the subtree. 1450 int size = NATIVE_ALLOCATION_SIZE; 1451 for (int i = 0; i < mChildren.size(); i++) { 1452 size += mChildren.get(i).getNativeSize(); 1453 } 1454 return size; 1455 } 1456 1457 @Override 1458 public boolean canApplyTheme() { 1459 if (mThemeAttrs != null) { 1460 return true; 1461 } 1462 1463 final ArrayList<VObject> children = mChildren; 1464 for (int i = 0, count = children.size(); i < count; i++) { 1465 final VObject child = children.get(i); 1466 if (child.canApplyTheme()) { 1467 return true; 1468 } 1469 } 1470 1471 return false; 1472 } 1473 1474 @Override 1475 public void applyTheme(Theme t) { 1476 if (mThemeAttrs != null) { 1477 final TypedArray a = t.resolveAttributes(mThemeAttrs, 1478 R.styleable.VectorDrawableGroup); 1479 updateStateFromTypedArray(a); 1480 a.recycle(); 1481 } 1482 1483 final ArrayList<VObject> children = mChildren; 1484 for (int i = 0, count = children.size(); i < count; i++) { 1485 final VObject child = children.get(i); 1486 if (child.canApplyTheme()) { 1487 child.applyTheme(t); 1488 1489 // Applying a theme may have made the child stateful. 1490 mIsStateful |= child.isStateful(); 1491 } 1492 } 1493 } 1494 1495 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 1496 @SuppressWarnings("unused") 1497 public float getRotation() { 1498 return isTreeValid() ? nGetRotation(mNativePtr) : 0; 1499 } 1500 1501 @SuppressWarnings("unused") 1502 public void setRotation(float rotation) { 1503 if (isTreeValid()) { 1504 nSetRotation(mNativePtr, rotation); 1505 } 1506 } 1507 1508 @SuppressWarnings("unused") 1509 public float getPivotX() { 1510 return isTreeValid() ? nGetPivotX(mNativePtr) : 0; 1511 } 1512 1513 @SuppressWarnings("unused") 1514 public void setPivotX(float pivotX) { 1515 if (isTreeValid()) { 1516 nSetPivotX(mNativePtr, pivotX); 1517 } 1518 } 1519 1520 @SuppressWarnings("unused") 1521 public float getPivotY() { 1522 return isTreeValid() ? nGetPivotY(mNativePtr) : 0; 1523 } 1524 1525 @SuppressWarnings("unused") 1526 public void setPivotY(float pivotY) { 1527 if (isTreeValid()) { 1528 nSetPivotY(mNativePtr, pivotY); 1529 } 1530 } 1531 1532 @SuppressWarnings("unused") 1533 public float getScaleX() { 1534 return isTreeValid() ? nGetScaleX(mNativePtr) : 0; 1535 } 1536 1537 @SuppressWarnings("unused") 1538 public void setScaleX(float scaleX) { 1539 if (isTreeValid()) { 1540 nSetScaleX(mNativePtr, scaleX); 1541 } 1542 } 1543 1544 @SuppressWarnings("unused") 1545 public float getScaleY() { 1546 return isTreeValid() ? nGetScaleY(mNativePtr) : 0; 1547 } 1548 1549 @SuppressWarnings("unused") 1550 public void setScaleY(float scaleY) { 1551 if (isTreeValid()) { 1552 nSetScaleY(mNativePtr, scaleY); 1553 } 1554 } 1555 1556 @SuppressWarnings("unused") 1557 public float getTranslateX() { 1558 return isTreeValid() ? nGetTranslateX(mNativePtr) : 0; 1559 } 1560 1561 @SuppressWarnings("unused") 1562 public void setTranslateX(float translateX) { 1563 if (isTreeValid()) { 1564 nSetTranslateX(mNativePtr, translateX); 1565 } 1566 } 1567 1568 @SuppressWarnings("unused") 1569 public float getTranslateY() { 1570 return isTreeValid() ? nGetTranslateY(mNativePtr) : 0; 1571 } 1572 1573 @SuppressWarnings("unused") 1574 public void setTranslateY(float translateY) { 1575 if (isTreeValid()) { 1576 nSetTranslateY(mNativePtr, translateY); 1577 } 1578 } 1579 } 1580 1581 /** 1582 * Common Path information for clip path and normal path. 1583 */ 1584 static abstract class VPath extends VObject { 1585 protected PathParser.PathData mPathData = null; 1586 1587 String mPathName; 1588 @Config int mChangingConfigurations; 1589 1590 private static final Property<VPath, PathParser.PathData> PATH_DATA = 1591 new Property<VPath, PathParser.PathData>(PathParser.PathData.class, "pathData") { 1592 @Override 1593 public void set(VPath object, PathParser.PathData data) { 1594 object.setPathData(data); 1595 } 1596 1597 @Override 1598 public PathParser.PathData get(VPath object) { 1599 return object.getPathData(); 1600 } 1601 }; 1602 1603 Property getProperty(String propertyName) { 1604 if (PATH_DATA.getName().equals(propertyName)) { 1605 return PATH_DATA; 1606 } 1607 // property not found 1608 return null; 1609 } 1610 1611 public VPath() { 1612 // Empty constructor. 1613 } 1614 1615 public VPath(VPath copy) { 1616 mPathName = copy.mPathName; 1617 mChangingConfigurations = copy.mChangingConfigurations; 1618 mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData); 1619 } 1620 1621 public String getPathName() { 1622 return mPathName; 1623 } 1624 1625 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 1626 @SuppressWarnings("unused") 1627 public PathParser.PathData getPathData() { 1628 return mPathData; 1629 } 1630 1631 // TODO: Move the PathEvaluator and this setter and the getter above into native. 1632 @SuppressWarnings("unused") 1633 public void setPathData(PathParser.PathData pathData) { 1634 mPathData.setPathData(pathData); 1635 if (isTreeValid()) { 1636 nSetPathData(getNativePtr(), mPathData.getNativePtr()); 1637 } 1638 } 1639 } 1640 1641 /** 1642 * Clip path, which only has name and pathData. 1643 */ 1644 private static class VClipPath extends VPath { 1645 private final long mNativePtr; 1646 private static final int NATIVE_ALLOCATION_SIZE = 120; 1647 1648 public VClipPath() { 1649 mNativePtr = nCreateClipPath(); 1650 } 1651 1652 public VClipPath(VClipPath copy) { 1653 super(copy); 1654 mNativePtr = nCreateClipPath(copy.mNativePtr); 1655 } 1656 1657 @Override 1658 public long getNativePtr() { 1659 return mNativePtr; 1660 } 1661 1662 @Override 1663 public void inflate(Resources r, AttributeSet attrs, Theme theme) { 1664 final TypedArray a = obtainAttributes(r, theme, attrs, 1665 R.styleable.VectorDrawableClipPath); 1666 updateStateFromTypedArray(a); 1667 a.recycle(); 1668 } 1669 1670 @Override 1671 public boolean canApplyTheme() { 1672 return false; 1673 } 1674 1675 @Override 1676 public void applyTheme(Theme theme) { 1677 // No-op. 1678 } 1679 1680 @Override 1681 public boolean onStateChange(int[] stateSet) { 1682 return false; 1683 } 1684 1685 @Override 1686 public boolean isStateful() { 1687 return false; 1688 } 1689 1690 @Override 1691 public boolean hasFocusStateSpecified() { 1692 return false; 1693 } 1694 1695 @Override 1696 int getNativeSize() { 1697 return NATIVE_ALLOCATION_SIZE; 1698 } 1699 1700 private void updateStateFromTypedArray(TypedArray a) { 1701 // Account for any configuration changes. 1702 mChangingConfigurations |= a.getChangingConfigurations(); 1703 1704 final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name); 1705 if (pathName != null) { 1706 mPathName = pathName; 1707 nSetName(mNativePtr, mPathName); 1708 } 1709 1710 final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData); 1711 if (pathDataString != null) { 1712 mPathData = new PathParser.PathData(pathDataString); 1713 nSetPathString(mNativePtr, pathDataString, pathDataString.length()); 1714 } 1715 } 1716 } 1717 1718 /** 1719 * Normal path, which contains all the fill / paint information. 1720 */ 1721 static class VFullPath extends VPath { 1722 private static final int STROKE_WIDTH_INDEX = 0; 1723 private static final int STROKE_COLOR_INDEX = 1; 1724 private static final int STROKE_ALPHA_INDEX = 2; 1725 private static final int FILL_COLOR_INDEX = 3; 1726 private static final int FILL_ALPHA_INDEX = 4; 1727 private static final int TRIM_PATH_START_INDEX = 5; 1728 private static final int TRIM_PATH_END_INDEX = 6; 1729 private static final int TRIM_PATH_OFFSET_INDEX = 7; 1730 private static final int STROKE_LINE_CAP_INDEX = 8; 1731 private static final int STROKE_LINE_JOIN_INDEX = 9; 1732 private static final int STROKE_MITER_LIMIT_INDEX = 10; 1733 private static final int FILL_TYPE_INDEX = 11; 1734 private static final int TOTAL_PROPERTY_COUNT = 12; 1735 1736 private static final int NATIVE_ALLOCATION_SIZE = 264; 1737 // Property map for animatable attributes. 1738 private final static HashMap<String, Integer> sPropertyIndexMap 1739 = new HashMap<String, Integer> () { 1740 { 1741 put("strokeWidth", STROKE_WIDTH_INDEX); 1742 put("strokeColor", STROKE_COLOR_INDEX); 1743 put("strokeAlpha", STROKE_ALPHA_INDEX); 1744 put("fillColor", FILL_COLOR_INDEX); 1745 put("fillAlpha", FILL_ALPHA_INDEX); 1746 put("trimPathStart", TRIM_PATH_START_INDEX); 1747 put("trimPathEnd", TRIM_PATH_END_INDEX); 1748 put("trimPathOffset", TRIM_PATH_OFFSET_INDEX); 1749 } 1750 }; 1751 1752 // Below are the Properties that wrap the setters to avoid reflection overhead in animations 1753 private static final Property<VFullPath, Float> STROKE_WIDTH = 1754 new FloatProperty<VFullPath> ("strokeWidth") { 1755 @Override 1756 public void setValue(VFullPath object, float value) { 1757 object.setStrokeWidth(value); 1758 } 1759 1760 @Override 1761 public Float get(VFullPath object) { 1762 return object.getStrokeWidth(); 1763 } 1764 }; 1765 1766 private static final Property<VFullPath, Integer> STROKE_COLOR = 1767 new IntProperty<VFullPath> ("strokeColor") { 1768 @Override 1769 public void setValue(VFullPath object, int value) { 1770 object.setStrokeColor(value); 1771 } 1772 1773 @Override 1774 public Integer get(VFullPath object) { 1775 return object.getStrokeColor(); 1776 } 1777 }; 1778 1779 private static final Property<VFullPath, Float> STROKE_ALPHA = 1780 new FloatProperty<VFullPath> ("strokeAlpha") { 1781 @Override 1782 public void setValue(VFullPath object, float value) { 1783 object.setStrokeAlpha(value); 1784 } 1785 1786 @Override 1787 public Float get(VFullPath object) { 1788 return object.getStrokeAlpha(); 1789 } 1790 }; 1791 1792 private static final Property<VFullPath, Integer> FILL_COLOR = 1793 new IntProperty<VFullPath>("fillColor") { 1794 @Override 1795 public void setValue(VFullPath object, int value) { 1796 object.setFillColor(value); 1797 } 1798 1799 @Override 1800 public Integer get(VFullPath object) { 1801 return object.getFillColor(); 1802 } 1803 }; 1804 1805 private static final Property<VFullPath, Float> FILL_ALPHA = 1806 new FloatProperty<VFullPath> ("fillAlpha") { 1807 @Override 1808 public void setValue(VFullPath object, float value) { 1809 object.setFillAlpha(value); 1810 } 1811 1812 @Override 1813 public Float get(VFullPath object) { 1814 return object.getFillAlpha(); 1815 } 1816 }; 1817 1818 private static final Property<VFullPath, Float> TRIM_PATH_START = 1819 new FloatProperty<VFullPath> ("trimPathStart") { 1820 @Override 1821 public void setValue(VFullPath object, float value) { 1822 object.setTrimPathStart(value); 1823 } 1824 1825 @Override 1826 public Float get(VFullPath object) { 1827 return object.getTrimPathStart(); 1828 } 1829 }; 1830 1831 private static final Property<VFullPath, Float> TRIM_PATH_END = 1832 new FloatProperty<VFullPath> ("trimPathEnd") { 1833 @Override 1834 public void setValue(VFullPath object, float value) { 1835 object.setTrimPathEnd(value); 1836 } 1837 1838 @Override 1839 public Float get(VFullPath object) { 1840 return object.getTrimPathEnd(); 1841 } 1842 }; 1843 1844 private static final Property<VFullPath, Float> TRIM_PATH_OFFSET = 1845 new FloatProperty<VFullPath> ("trimPathOffset") { 1846 @Override 1847 public void setValue(VFullPath object, float value) { 1848 object.setTrimPathOffset(value); 1849 } 1850 1851 @Override 1852 public Float get(VFullPath object) { 1853 return object.getTrimPathOffset(); 1854 } 1855 }; 1856 1857 private final static HashMap<String, Property> sPropertyMap 1858 = new HashMap<String, Property> () { 1859 { 1860 put("strokeWidth", STROKE_WIDTH); 1861 put("strokeColor", STROKE_COLOR); 1862 put("strokeAlpha", STROKE_ALPHA); 1863 put("fillColor", FILL_COLOR); 1864 put("fillAlpha", FILL_ALPHA); 1865 put("trimPathStart", TRIM_PATH_START); 1866 put("trimPathEnd", TRIM_PATH_END); 1867 put("trimPathOffset", TRIM_PATH_OFFSET); 1868 } 1869 }; 1870 1871 // Temp array to store property data obtained from native getter. 1872 private byte[] mPropertyData; 1873 ///////////////////////////////////////////////////// 1874 // Variables below need to be copied (deep copy if applicable) for mutation. 1875 private int[] mThemeAttrs; 1876 1877 ComplexColor mStrokeColors = null; 1878 ComplexColor mFillColors = null; 1879 private final long mNativePtr; 1880 1881 public VFullPath() { 1882 mNativePtr = nCreateFullPath(); 1883 } 1884 1885 public VFullPath(VFullPath copy) { 1886 super(copy); 1887 mNativePtr = nCreateFullPath(copy.mNativePtr); 1888 mThemeAttrs = copy.mThemeAttrs; 1889 mStrokeColors = copy.mStrokeColors; 1890 mFillColors = copy.mFillColors; 1891 } 1892 1893 Property getProperty(String propertyName) { 1894 Property p = super.getProperty(propertyName); 1895 if (p != null) { 1896 return p; 1897 } 1898 if (sPropertyMap.containsKey(propertyName)) { 1899 return sPropertyMap.get(propertyName); 1900 } else { 1901 // property not found 1902 return null; 1903 } 1904 } 1905 1906 int getPropertyIndex(String propertyName) { 1907 if (!sPropertyIndexMap.containsKey(propertyName)) { 1908 return -1; 1909 } else { 1910 return sPropertyIndexMap.get(propertyName); 1911 } 1912 } 1913 1914 @Override 1915 public boolean onStateChange(int[] stateSet) { 1916 boolean changed = false; 1917 1918 if (mStrokeColors != null && mStrokeColors instanceof ColorStateList) { 1919 final int oldStrokeColor = getStrokeColor(); 1920 final int newStrokeColor = 1921 ((ColorStateList) mStrokeColors).getColorForState(stateSet, oldStrokeColor); 1922 changed |= oldStrokeColor != newStrokeColor; 1923 if (oldStrokeColor != newStrokeColor) { 1924 nSetStrokeColor(mNativePtr, newStrokeColor); 1925 } 1926 } 1927 1928 if (mFillColors != null && mFillColors instanceof ColorStateList) { 1929 final int oldFillColor = getFillColor(); 1930 final int newFillColor = ((ColorStateList) mFillColors).getColorForState(stateSet, oldFillColor); 1931 changed |= oldFillColor != newFillColor; 1932 if (oldFillColor != newFillColor) { 1933 nSetFillColor(mNativePtr, newFillColor); 1934 } 1935 } 1936 1937 return changed; 1938 } 1939 1940 @Override 1941 public boolean isStateful() { 1942 return mStrokeColors != null || mFillColors != null; 1943 } 1944 1945 @Override 1946 public boolean hasFocusStateSpecified() { 1947 return (mStrokeColors != null && mStrokeColors instanceof ColorStateList && 1948 ((ColorStateList) mStrokeColors).hasFocusStateSpecified()) && 1949 (mFillColors != null && mFillColors instanceof ColorStateList && 1950 ((ColorStateList) mFillColors).hasFocusStateSpecified()); 1951 } 1952 1953 @Override 1954 int getNativeSize() { 1955 return NATIVE_ALLOCATION_SIZE; 1956 } 1957 1958 @Override 1959 public long getNativePtr() { 1960 return mNativePtr; 1961 } 1962 1963 @Override 1964 public void inflate(Resources r, AttributeSet attrs, Theme theme) { 1965 final TypedArray a = obtainAttributes(r, theme, attrs, 1966 R.styleable.VectorDrawablePath); 1967 updateStateFromTypedArray(a); 1968 a.recycle(); 1969 } 1970 1971 private void updateStateFromTypedArray(TypedArray a) { 1972 int byteCount = TOTAL_PROPERTY_COUNT * 4; 1973 if (mPropertyData == null) { 1974 // Lazy initialization: If the path is created through copy constructor, this may 1975 // never get called. 1976 mPropertyData = new byte[byteCount]; 1977 } 1978 // The bulk getters/setters of property data (e.g. stroke width, color, etc) allows us 1979 // to pull current values from native and store modifications with only two methods, 1980 // minimizing JNI overhead. 1981 boolean success = nGetFullPathProperties(mNativePtr, mPropertyData, byteCount); 1982 if (!success) { 1983 throw new RuntimeException("Error: inconsistent property count"); 1984 } 1985 1986 ByteBuffer properties = ByteBuffer.wrap(mPropertyData); 1987 properties.order(ByteOrder.nativeOrder()); 1988 float strokeWidth = properties.getFloat(STROKE_WIDTH_INDEX * 4); 1989 int strokeColor = properties.getInt(STROKE_COLOR_INDEX * 4); 1990 float strokeAlpha = properties.getFloat(STROKE_ALPHA_INDEX * 4); 1991 int fillColor = properties.getInt(FILL_COLOR_INDEX * 4); 1992 float fillAlpha = properties.getFloat(FILL_ALPHA_INDEX * 4); 1993 float trimPathStart = properties.getFloat(TRIM_PATH_START_INDEX * 4); 1994 float trimPathEnd = properties.getFloat(TRIM_PATH_END_INDEX * 4); 1995 float trimPathOffset = properties.getFloat(TRIM_PATH_OFFSET_INDEX * 4); 1996 int strokeLineCap = properties.getInt(STROKE_LINE_CAP_INDEX * 4); 1997 int strokeLineJoin = properties.getInt(STROKE_LINE_JOIN_INDEX * 4); 1998 float strokeMiterLimit = properties.getFloat(STROKE_MITER_LIMIT_INDEX * 4); 1999 int fillType = properties.getInt(FILL_TYPE_INDEX * 4); 2000 Shader fillGradient = null; 2001 Shader strokeGradient = null; 2002 // Account for any configuration changes. 2003 mChangingConfigurations |= a.getChangingConfigurations(); 2004 2005 // Extract the theme attributes, if any. 2006 mThemeAttrs = a.extractThemeAttrs(); 2007 2008 final String pathName = a.getString(R.styleable.VectorDrawablePath_name); 2009 if (pathName != null) { 2010 mPathName = pathName; 2011 nSetName(mNativePtr, mPathName); 2012 } 2013 2014 final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData); 2015 if (pathString != null) { 2016 mPathData = new PathParser.PathData(pathString); 2017 nSetPathString(mNativePtr, pathString, pathString.length()); 2018 } 2019 2020 final ComplexColor fillColors = a.getComplexColor( 2021 R.styleable.VectorDrawablePath_fillColor); 2022 if (fillColors != null) { 2023 // If the colors is a gradient color, or the color state list is stateful, keep the 2024 // colors information. Otherwise, discard the colors and keep the default color. 2025 if (fillColors instanceof GradientColor) { 2026 mFillColors = fillColors; 2027 fillGradient = ((GradientColor) fillColors).getShader(); 2028 } else if (fillColors.isStateful()) { 2029 mFillColors = fillColors; 2030 } else { 2031 mFillColors = null; 2032 } 2033 fillColor = fillColors.getDefaultColor(); 2034 } 2035 2036 final ComplexColor strokeColors = a.getComplexColor( 2037 R.styleable.VectorDrawablePath_strokeColor); 2038 if (strokeColors != null) { 2039 // If the colors is a gradient color, or the color state list is stateful, keep the 2040 // colors information. Otherwise, discard the colors and keep the default color. 2041 if (strokeColors instanceof GradientColor) { 2042 mStrokeColors = strokeColors; 2043 strokeGradient = ((GradientColor) strokeColors).getShader(); 2044 } else if (strokeColors.isStateful()) { 2045 mStrokeColors = strokeColors; 2046 } else { 2047 mStrokeColors = null; 2048 } 2049 strokeColor = strokeColors.getDefaultColor(); 2050 } 2051 // Update the gradient info, even if the gradiet is null. 2052 nUpdateFullPathFillGradient(mNativePtr, 2053 fillGradient != null ? fillGradient.getNativeInstance() : 0); 2054 nUpdateFullPathStrokeGradient(mNativePtr, 2055 strokeGradient != null ? strokeGradient.getNativeInstance() : 0); 2056 2057 fillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, fillAlpha); 2058 2059 strokeLineCap = a.getInt( 2060 R.styleable.VectorDrawablePath_strokeLineCap, strokeLineCap); 2061 strokeLineJoin = a.getInt( 2062 R.styleable.VectorDrawablePath_strokeLineJoin, strokeLineJoin); 2063 strokeMiterLimit = a.getFloat( 2064 R.styleable.VectorDrawablePath_strokeMiterLimit, strokeMiterLimit); 2065 strokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha, 2066 strokeAlpha); 2067 strokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, 2068 strokeWidth); 2069 trimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, 2070 trimPathEnd); 2071 trimPathOffset = a.getFloat( 2072 R.styleable.VectorDrawablePath_trimPathOffset, trimPathOffset); 2073 trimPathStart = a.getFloat( 2074 R.styleable.VectorDrawablePath_trimPathStart, trimPathStart); 2075 fillType = a.getInt(R.styleable.VectorDrawablePath_fillType, fillType); 2076 2077 nUpdateFullPathProperties(mNativePtr, strokeWidth, strokeColor, strokeAlpha, 2078 fillColor, fillAlpha, trimPathStart, trimPathEnd, trimPathOffset, 2079 strokeMiterLimit, strokeLineCap, strokeLineJoin, fillType); 2080 } 2081 2082 @Override 2083 public boolean canApplyTheme() { 2084 if (mThemeAttrs != null) { 2085 return true; 2086 } 2087 2088 boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors); 2089 boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors); 2090 if (fillCanApplyTheme || strokeCanApplyTheme) { 2091 return true; 2092 } 2093 return false; 2094 2095 } 2096 2097 @Override 2098 public void applyTheme(Theme t) { 2099 // Resolve the theme attributes directly referred by the VectorDrawable. 2100 if (mThemeAttrs != null) { 2101 final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath); 2102 updateStateFromTypedArray(a); 2103 a.recycle(); 2104 } 2105 2106 // Resolve the theme attributes in-directly referred by the VectorDrawable, for example, 2107 // fillColor can refer to a color state list which itself needs to apply theme. 2108 // And this is the reason we still want to keep partial update for the path's properties. 2109 boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors); 2110 boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors); 2111 2112 if (fillCanApplyTheme) { 2113 mFillColors = mFillColors.obtainForTheme(t); 2114 if (mFillColors instanceof GradientColor) { 2115 nUpdateFullPathFillGradient(mNativePtr, 2116 ((GradientColor) mFillColors).getShader().getNativeInstance()); 2117 } else if (mFillColors instanceof ColorStateList) { 2118 nSetFillColor(mNativePtr, mFillColors.getDefaultColor()); 2119 } 2120 } 2121 2122 if (strokeCanApplyTheme) { 2123 mStrokeColors = mStrokeColors.obtainForTheme(t); 2124 if (mStrokeColors instanceof GradientColor) { 2125 nUpdateFullPathStrokeGradient(mNativePtr, 2126 ((GradientColor) mStrokeColors).getShader().getNativeInstance()); 2127 } else if (mStrokeColors instanceof ColorStateList) { 2128 nSetStrokeColor(mNativePtr, mStrokeColors.getDefaultColor()); 2129 } 2130 } 2131 } 2132 2133 private boolean canComplexColorApplyTheme(ComplexColor complexColor) { 2134 return complexColor != null && complexColor.canApplyTheme(); 2135 } 2136 2137 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 2138 @SuppressWarnings("unused") 2139 int getStrokeColor() { 2140 return isTreeValid() ? nGetStrokeColor(mNativePtr) : 0; 2141 } 2142 2143 @SuppressWarnings("unused") 2144 void setStrokeColor(int strokeColor) { 2145 mStrokeColors = null; 2146 if (isTreeValid()) { 2147 nSetStrokeColor(mNativePtr, strokeColor); 2148 } 2149 } 2150 2151 @SuppressWarnings("unused") 2152 float getStrokeWidth() { 2153 return isTreeValid() ? nGetStrokeWidth(mNativePtr) : 0; 2154 } 2155 2156 @SuppressWarnings("unused") 2157 void setStrokeWidth(float strokeWidth) { 2158 if (isTreeValid()) { 2159 nSetStrokeWidth(mNativePtr, strokeWidth); 2160 } 2161 } 2162 2163 @SuppressWarnings("unused") 2164 float getStrokeAlpha() { 2165 return isTreeValid() ? nGetStrokeAlpha(mNativePtr) : 0; 2166 } 2167 2168 @SuppressWarnings("unused") 2169 void setStrokeAlpha(float strokeAlpha) { 2170 if (isTreeValid()) { 2171 nSetStrokeAlpha(mNativePtr, strokeAlpha); 2172 } 2173 } 2174 2175 @SuppressWarnings("unused") 2176 int getFillColor() { 2177 return isTreeValid() ? nGetFillColor(mNativePtr) : 0; 2178 } 2179 2180 @SuppressWarnings("unused") 2181 void setFillColor(int fillColor) { 2182 mFillColors = null; 2183 if (isTreeValid()) { 2184 nSetFillColor(mNativePtr, fillColor); 2185 } 2186 } 2187 2188 @SuppressWarnings("unused") 2189 float getFillAlpha() { 2190 return isTreeValid() ? nGetFillAlpha(mNativePtr) : 0; 2191 } 2192 2193 @SuppressWarnings("unused") 2194 void setFillAlpha(float fillAlpha) { 2195 if (isTreeValid()) { 2196 nSetFillAlpha(mNativePtr, fillAlpha); 2197 } 2198 } 2199 2200 @SuppressWarnings("unused") 2201 float getTrimPathStart() { 2202 return isTreeValid() ? nGetTrimPathStart(mNativePtr) : 0; 2203 } 2204 2205 @SuppressWarnings("unused") 2206 void setTrimPathStart(float trimPathStart) { 2207 if (isTreeValid()) { 2208 nSetTrimPathStart(mNativePtr, trimPathStart); 2209 } 2210 } 2211 2212 @SuppressWarnings("unused") 2213 float getTrimPathEnd() { 2214 return isTreeValid() ? nGetTrimPathEnd(mNativePtr) : 0; 2215 } 2216 2217 @SuppressWarnings("unused") 2218 void setTrimPathEnd(float trimPathEnd) { 2219 if (isTreeValid()) { 2220 nSetTrimPathEnd(mNativePtr, trimPathEnd); 2221 } 2222 } 2223 2224 @SuppressWarnings("unused") 2225 float getTrimPathOffset() { 2226 return isTreeValid() ? nGetTrimPathOffset(mNativePtr) : 0; 2227 } 2228 2229 @SuppressWarnings("unused") 2230 void setTrimPathOffset(float trimPathOffset) { 2231 if (isTreeValid()) { 2232 nSetTrimPathOffset(mNativePtr, trimPathOffset); 2233 } 2234 } 2235 } 2236 2237 abstract static class VObject { 2238 VirtualRefBasePtr mTreePtr = null; 2239 boolean isTreeValid() { 2240 return mTreePtr != null && mTreePtr.get() != 0; 2241 } 2242 void setTree(VirtualRefBasePtr ptr) { 2243 mTreePtr = ptr; 2244 } 2245 abstract long getNativePtr(); 2246 abstract void inflate(Resources r, AttributeSet attrs, Theme theme); 2247 abstract boolean canApplyTheme(); 2248 abstract void applyTheme(Theme t); 2249 abstract boolean onStateChange(int[] state); 2250 abstract boolean isStateful(); 2251 abstract boolean hasFocusStateSpecified(); 2252 abstract int getNativeSize(); 2253 abstract Property getProperty(String propertyName); 2254 } 2255 2256 private static native int nDraw(long rendererPtr, long canvasWrapperPtr, 2257 long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache); 2258 private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties, 2259 int length); 2260 private static native void nSetName(long nodePtr, String name); 2261 private static native boolean nGetGroupProperties(long groupPtr, float[] properties, 2262 int length); 2263 private static native void nSetPathString(long pathPtr, String pathString, int length); 2264 2265 // ------------- @FastNative ------------------ 2266 2267 @FastNative 2268 private static native long nCreateTree(long rootGroupPtr); 2269 @FastNative 2270 private static native long nCreateTreeFromCopy(long treeToCopy, long rootGroupPtr); 2271 @FastNative 2272 private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth, 2273 float viewportHeight); 2274 @FastNative 2275 private static native boolean nSetRootAlpha(long rendererPtr, float alpha); 2276 @FastNative 2277 private static native float nGetRootAlpha(long rendererPtr); 2278 @FastNative 2279 private static native void nSetAntiAlias(long rendererPtr, boolean aa); 2280 @FastNative 2281 private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching); 2282 2283 @FastNative 2284 private static native long nCreateFullPath(); 2285 @FastNative 2286 private static native long nCreateFullPath(long nativeFullPathPtr); 2287 2288 @FastNative 2289 private static native void nUpdateFullPathProperties(long pathPtr, float strokeWidth, 2290 int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart, 2291 float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap, 2292 int strokeLineJoin, int fillType); 2293 @FastNative 2294 private static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr); 2295 @FastNative 2296 private static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr); 2297 2298 @FastNative 2299 private static native long nCreateClipPath(); 2300 @FastNative 2301 private static native long nCreateClipPath(long clipPathPtr); 2302 2303 @FastNative 2304 private static native long nCreateGroup(); 2305 @FastNative 2306 private static native long nCreateGroup(long groupPtr); 2307 @FastNative 2308 private static native void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX, 2309 float pivotY, float scaleX, float scaleY, float translateX, float translateY); 2310 2311 @FastNative 2312 private static native void nAddChild(long groupPtr, long nodePtr); 2313 2314 /** 2315 * The setters and getters below for paths and groups are here temporarily, and will be 2316 * removed once the animation in AVD is replaced with RenderNodeAnimator, in which case the 2317 * animation will modify these properties in native. By then no JNI hopping would be necessary 2318 * for VD during animation, and these setters and getters will be obsolete. 2319 */ 2320 // Setters and getters during animation. 2321 @FastNative 2322 private static native float nGetRotation(long groupPtr); 2323 @FastNative 2324 private static native void nSetRotation(long groupPtr, float rotation); 2325 @FastNative 2326 private static native float nGetPivotX(long groupPtr); 2327 @FastNative 2328 private static native void nSetPivotX(long groupPtr, float pivotX); 2329 @FastNative 2330 private static native float nGetPivotY(long groupPtr); 2331 @FastNative 2332 private static native void nSetPivotY(long groupPtr, float pivotY); 2333 @FastNative 2334 private static native float nGetScaleX(long groupPtr); 2335 @FastNative 2336 private static native void nSetScaleX(long groupPtr, float scaleX); 2337 @FastNative 2338 private static native float nGetScaleY(long groupPtr); 2339 @FastNative 2340 private static native void nSetScaleY(long groupPtr, float scaleY); 2341 @FastNative 2342 private static native float nGetTranslateX(long groupPtr); 2343 @FastNative 2344 private static native void nSetTranslateX(long groupPtr, float translateX); 2345 @FastNative 2346 private static native float nGetTranslateY(long groupPtr); 2347 @FastNative 2348 private static native void nSetTranslateY(long groupPtr, float translateY); 2349 2350 // Setters and getters for VPath during animation. 2351 @FastNative 2352 private static native void nSetPathData(long pathPtr, long pathDataPtr); 2353 @FastNative 2354 private static native float nGetStrokeWidth(long pathPtr); 2355 @FastNative 2356 private static native void nSetStrokeWidth(long pathPtr, float width); 2357 @FastNative 2358 private static native int nGetStrokeColor(long pathPtr); 2359 @FastNative 2360 private static native void nSetStrokeColor(long pathPtr, int strokeColor); 2361 @FastNative 2362 private static native float nGetStrokeAlpha(long pathPtr); 2363 @FastNative 2364 private static native void nSetStrokeAlpha(long pathPtr, float alpha); 2365 @FastNative 2366 private static native int nGetFillColor(long pathPtr); 2367 @FastNative 2368 private static native void nSetFillColor(long pathPtr, int fillColor); 2369 @FastNative 2370 private static native float nGetFillAlpha(long pathPtr); 2371 @FastNative 2372 private static native void nSetFillAlpha(long pathPtr, float fillAlpha); 2373 @FastNative 2374 private static native float nGetTrimPathStart(long pathPtr); 2375 @FastNative 2376 private static native void nSetTrimPathStart(long pathPtr, float trimPathStart); 2377 @FastNative 2378 private static native float nGetTrimPathEnd(long pathPtr); 2379 @FastNative 2380 private static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd); 2381 @FastNative 2382 private static native float nGetTrimPathOffset(long pathPtr); 2383 @FastNative 2384 private static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset); 2385 } 2386