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