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