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