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