VectorDrawable.java revision 792926a58c94563cf35d532cd3db888cc1cbeb7d
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 createNativeTreeFromCopy(copy, 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 // Create a new native tree with the given root group, and copy the properties from the 853 // given VectorDrawableState's native tree. 854 private void createNativeTreeFromCopy(VectorDrawableState copy, VGroup rootGroup) { 855 mNativeTree = new VirtualRefBasePtr(nCreateTreeFromCopy( 856 copy.mNativeTree.get(), rootGroup.mNativePtr)); 857 // Register tree size 858 VMRuntime.getRuntime().registerNativeAllocation(NATIVE_ALLOCATION_SIZE); 859 } 860 861 862 void onTreeConstructionFinished() { 863 mRootGroup.setTree(mNativeTree); 864 mAllocationOfAllNodes = mRootGroup.getNativeSize(); 865 VMRuntime.getRuntime().registerNativeAllocation(mAllocationOfAllNodes); 866 } 867 868 long getNativeRenderer() { 869 if (mNativeTree == null) { 870 return 0; 871 } 872 return mNativeTree.get(); 873 } 874 875 public boolean canReuseCache() { 876 if (!mCacheDirty 877 && mCachedThemeAttrs == mThemeAttrs 878 && mCachedTint == mTint 879 && mCachedTintMode == mTintMode 880 && mCachedAutoMirrored == mAutoMirrored) { 881 return true; 882 } 883 updateCacheStates(); 884 return false; 885 } 886 887 public void updateCacheStates() { 888 // Use shallow copy here and shallow comparison in canReuseCache(), 889 // likely hit cache miss more, but practically not much difference. 890 mCachedThemeAttrs = mThemeAttrs; 891 mCachedTint = mTint; 892 mCachedTintMode = mTintMode; 893 mCachedAutoMirrored = mAutoMirrored; 894 mCacheDirty = false; 895 } 896 897 public void applyTheme(Theme t) { 898 mRootGroup.applyTheme(t); 899 } 900 901 @Override 902 public boolean canApplyTheme() { 903 return mThemeAttrs != null 904 || (mRootGroup != null && mRootGroup.canApplyTheme()) 905 || (mTint != null && mTint.canApplyTheme()) 906 || super.canApplyTheme(); 907 } 908 909 public VectorDrawableState() { 910 mRootGroup = new VGroup(); 911 createNativeTree(mRootGroup); 912 } 913 914 @Override 915 public Drawable newDrawable() { 916 return new VectorDrawable(this, null); 917 } 918 919 @Override 920 public Drawable newDrawable(Resources res) { 921 return new VectorDrawable(this, res); 922 } 923 924 @Override 925 public @Config int getChangingConfigurations() { 926 return mChangingConfigurations 927 | (mTint != null ? mTint.getChangingConfigurations() : 0); 928 } 929 930 public boolean isStateful() { 931 return (mTint != null && mTint.isStateful()) 932 || (mRootGroup != null && mRootGroup.isStateful()); 933 } 934 935 void setViewportSize(float viewportWidth, float viewportHeight) { 936 mViewportWidth = viewportWidth; 937 mViewportHeight = viewportHeight; 938 nSetRendererViewportSize(getNativeRenderer(), viewportWidth, viewportHeight); 939 } 940 941 public final boolean setDensity(int targetDensity) { 942 if (mDensity != targetDensity) { 943 final int sourceDensity = mDensity; 944 mDensity = targetDensity; 945 applyDensityScaling(sourceDensity, targetDensity); 946 return true; 947 } 948 return false; 949 } 950 951 private void applyDensityScaling(int sourceDensity, int targetDensity) { 952 mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity); 953 mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity); 954 955 final int insetLeft = Drawable.scaleFromDensity( 956 mOpticalInsets.left, sourceDensity, targetDensity, false); 957 final int insetTop = Drawable.scaleFromDensity( 958 mOpticalInsets.top, sourceDensity, targetDensity, false); 959 final int insetRight = Drawable.scaleFromDensity( 960 mOpticalInsets.right, sourceDensity, targetDensity, false); 961 final int insetBottom = Drawable.scaleFromDensity( 962 mOpticalInsets.bottom, sourceDensity, targetDensity, false); 963 mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom); 964 } 965 966 public boolean onStateChange(int[] stateSet) { 967 return mRootGroup.onStateChange(stateSet); 968 } 969 970 @Override 971 public void finalize() throws Throwable { 972 super.finalize(); 973 int bitmapCacheSize = mLastHWCachePixelCount * 4 + mLastSWCachePixelCount * 4; 974 VMRuntime.getRuntime().registerNativeFree(NATIVE_ALLOCATION_SIZE 975 + mAllocationOfAllNodes + bitmapCacheSize); 976 } 977 978 /** 979 * setAlpha() and getAlpha() are used mostly for animation purpose. Return true if alpha 980 * has changed. 981 */ 982 public boolean setAlpha(float alpha) { 983 return nSetRootAlpha(mNativeTree.get(), alpha); 984 } 985 986 @SuppressWarnings("unused") 987 public float getAlpha() { 988 return nGetRootAlpha(mNativeTree.get()); 989 } 990 } 991 992 static class VGroup extends VObject { 993 private static final int ROTATION_INDEX = 0; 994 private static final int PIVOT_X_INDEX = 1; 995 private static final int PIVOT_Y_INDEX = 2; 996 private static final int SCALE_X_INDEX = 3; 997 private static final int SCALE_Y_INDEX = 4; 998 private static final int TRANSLATE_X_INDEX = 5; 999 private static final int TRANSLATE_Y_INDEX = 6; 1000 private static final int TRANSFORM_PROPERTY_COUNT = 7; 1001 1002 private static final int NATIVE_ALLOCATION_SIZE = 100; 1003 1004 private static final HashMap<String, Integer> sPropertyIndexMap = 1005 new HashMap<String, Integer>() { 1006 { 1007 put("translateX", TRANSLATE_X_INDEX); 1008 put("translateY", TRANSLATE_Y_INDEX); 1009 put("scaleX", SCALE_X_INDEX); 1010 put("scaleY", SCALE_Y_INDEX); 1011 put("pivotX", PIVOT_X_INDEX); 1012 put("pivotY", PIVOT_Y_INDEX); 1013 put("rotation", ROTATION_INDEX); 1014 } 1015 }; 1016 1017 static int getPropertyIndex(String propertyName) { 1018 if (sPropertyIndexMap.containsKey(propertyName)) { 1019 return sPropertyIndexMap.get(propertyName); 1020 } else { 1021 // property not found 1022 return -1; 1023 } 1024 } 1025 1026 // Below are the Properties that wrap the setters to avoid reflection overhead in animations 1027 private static final Property<VGroup, Float> TRANSLATE_X = 1028 new FloatProperty<VGroup> ("translateX") { 1029 @Override 1030 public void setValue(VGroup object, float value) { 1031 object.setTranslateX(value); 1032 } 1033 1034 @Override 1035 public Float get(VGroup object) { 1036 return object.getTranslateX(); 1037 } 1038 }; 1039 1040 private static final Property<VGroup, Float> TRANSLATE_Y = 1041 new FloatProperty<VGroup> ("translateY") { 1042 @Override 1043 public void setValue(VGroup object, float value) { 1044 object.setTranslateY(value); 1045 } 1046 1047 @Override 1048 public Float get(VGroup object) { 1049 return object.getTranslateY(); 1050 } 1051 }; 1052 1053 private static final Property<VGroup, Float> SCALE_X = 1054 new FloatProperty<VGroup> ("scaleX") { 1055 @Override 1056 public void setValue(VGroup object, float value) { 1057 object.setScaleX(value); 1058 } 1059 1060 @Override 1061 public Float get(VGroup object) { 1062 return object.getScaleX(); 1063 } 1064 }; 1065 1066 private static final Property<VGroup, Float> SCALE_Y = 1067 new FloatProperty<VGroup> ("scaleY") { 1068 @Override 1069 public void setValue(VGroup object, float value) { 1070 object.setScaleY(value); 1071 } 1072 1073 @Override 1074 public Float get(VGroup object) { 1075 return object.getScaleY(); 1076 } 1077 }; 1078 1079 private static final Property<VGroup, Float> PIVOT_X = 1080 new FloatProperty<VGroup> ("pivotX") { 1081 @Override 1082 public void setValue(VGroup object, float value) { 1083 object.setPivotX(value); 1084 } 1085 1086 @Override 1087 public Float get(VGroup object) { 1088 return object.getPivotX(); 1089 } 1090 }; 1091 1092 private static final Property<VGroup, Float> PIVOT_Y = 1093 new FloatProperty<VGroup> ("pivotY") { 1094 @Override 1095 public void setValue(VGroup object, float value) { 1096 object.setPivotY(value); 1097 } 1098 1099 @Override 1100 public Float get(VGroup object) { 1101 return object.getPivotY(); 1102 } 1103 }; 1104 1105 private static final Property<VGroup, Float> ROTATION = 1106 new FloatProperty<VGroup> ("rotation") { 1107 @Override 1108 public void setValue(VGroup object, float value) { 1109 object.setRotation(value); 1110 } 1111 1112 @Override 1113 public Float get(VGroup object) { 1114 return object.getRotation(); 1115 } 1116 }; 1117 1118 private static final HashMap<String, Property> sPropertyMap = 1119 new HashMap<String, Property>() { 1120 { 1121 put("translateX", TRANSLATE_X); 1122 put("translateY", TRANSLATE_Y); 1123 put("scaleX", SCALE_X); 1124 put("scaleY", SCALE_Y); 1125 put("pivotX", PIVOT_X); 1126 put("pivotY", PIVOT_Y); 1127 put("rotation", ROTATION); 1128 } 1129 }; 1130 // Temp array to store transform values obtained from native. 1131 private float[] mTransform; 1132 ///////////////////////////////////////////////////// 1133 // Variables below need to be copied (deep copy if applicable) for mutation. 1134 private final ArrayList<VObject> mChildren = new ArrayList<>(); 1135 private boolean mIsStateful; 1136 1137 // mLocalMatrix is updated based on the update of transformation information, 1138 // either parsed from the XML or by animation. 1139 private @Config int mChangingConfigurations; 1140 private int[] mThemeAttrs; 1141 private String mGroupName = null; 1142 1143 // The native object will be created in the constructor and will be destroyed in native 1144 // when the neither java nor native has ref to the tree. This pointer should be valid 1145 // throughout this VGroup Java object's life. 1146 private final long mNativePtr; 1147 public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) { 1148 1149 mIsStateful = copy.mIsStateful; 1150 mThemeAttrs = copy.mThemeAttrs; 1151 mGroupName = copy.mGroupName; 1152 mChangingConfigurations = copy.mChangingConfigurations; 1153 if (mGroupName != null) { 1154 targetsMap.put(mGroupName, this); 1155 } 1156 mNativePtr = nCreateGroup(copy.mNativePtr); 1157 1158 final ArrayList<VObject> children = copy.mChildren; 1159 for (int i = 0; i < children.size(); i++) { 1160 final VObject copyChild = children.get(i); 1161 if (copyChild instanceof VGroup) { 1162 final VGroup copyGroup = (VGroup) copyChild; 1163 addChild(new VGroup(copyGroup, targetsMap)); 1164 } else { 1165 final VPath newPath; 1166 if (copyChild instanceof VFullPath) { 1167 newPath = new VFullPath((VFullPath) copyChild); 1168 } else if (copyChild instanceof VClipPath) { 1169 newPath = new VClipPath((VClipPath) copyChild); 1170 } else { 1171 throw new IllegalStateException("Unknown object in the tree!"); 1172 } 1173 addChild(newPath); 1174 if (newPath.mPathName != null) { 1175 targetsMap.put(newPath.mPathName, newPath); 1176 } 1177 } 1178 } 1179 } 1180 1181 public VGroup() { 1182 mNativePtr = nCreateGroup(); 1183 } 1184 1185 Property getProperty(String propertyName) { 1186 if (sPropertyMap.containsKey(propertyName)) { 1187 return sPropertyMap.get(propertyName); 1188 } else { 1189 // property not found 1190 return null; 1191 } 1192 } 1193 1194 public String getGroupName() { 1195 return mGroupName; 1196 } 1197 1198 public void addChild(VObject child) { 1199 nAddChild(mNativePtr, child.getNativePtr()); 1200 mChildren.add(child); 1201 mIsStateful |= child.isStateful(); 1202 } 1203 1204 @Override 1205 public void setTree(VirtualRefBasePtr treeRoot) { 1206 super.setTree(treeRoot); 1207 for (int i = 0; i < mChildren.size(); i++) { 1208 mChildren.get(i).setTree(treeRoot); 1209 } 1210 } 1211 1212 @Override 1213 public long getNativePtr() { 1214 return mNativePtr; 1215 } 1216 1217 @Override 1218 public void inflate(Resources res, AttributeSet attrs, Theme theme) { 1219 final TypedArray a = obtainAttributes(res, theme, attrs, 1220 R.styleable.VectorDrawableGroup); 1221 updateStateFromTypedArray(a); 1222 a.recycle(); 1223 } 1224 1225 void updateStateFromTypedArray(TypedArray a) { 1226 // Account for any configuration changes. 1227 mChangingConfigurations |= a.getChangingConfigurations(); 1228 1229 // Extract the theme attributes, if any. 1230 mThemeAttrs = a.extractThemeAttrs(); 1231 if (mTransform == null) { 1232 // Lazy initialization: If the group is created through copy constructor, this may 1233 // never get called. 1234 mTransform = new float[TRANSFORM_PROPERTY_COUNT]; 1235 } 1236 boolean success = nGetGroupProperties(mNativePtr, mTransform, TRANSFORM_PROPERTY_COUNT); 1237 if (!success) { 1238 throw new RuntimeException("Error: inconsistent property count"); 1239 } 1240 float rotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, 1241 mTransform[ROTATION_INDEX]); 1242 float pivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, 1243 mTransform[PIVOT_X_INDEX]); 1244 float pivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, 1245 mTransform[PIVOT_Y_INDEX]); 1246 float scaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, 1247 mTransform[SCALE_X_INDEX]); 1248 float scaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, 1249 mTransform[SCALE_Y_INDEX]); 1250 float translateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, 1251 mTransform[TRANSLATE_X_INDEX]); 1252 float translateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, 1253 mTransform[TRANSLATE_Y_INDEX]); 1254 1255 final String groupName = a.getString(R.styleable.VectorDrawableGroup_name); 1256 if (groupName != null) { 1257 mGroupName = groupName; 1258 nSetName(mNativePtr, mGroupName); 1259 } 1260 nUpdateGroupProperties(mNativePtr, rotate, pivotX, pivotY, scaleX, scaleY, 1261 translateX, translateY); 1262 } 1263 1264 @Override 1265 public boolean onStateChange(int[] stateSet) { 1266 boolean changed = false; 1267 1268 final ArrayList<VObject> children = mChildren; 1269 for (int i = 0, count = children.size(); i < count; i++) { 1270 final VObject child = children.get(i); 1271 if (child.isStateful()) { 1272 changed |= child.onStateChange(stateSet); 1273 } 1274 } 1275 1276 return changed; 1277 } 1278 1279 @Override 1280 public boolean isStateful() { 1281 return mIsStateful; 1282 } 1283 1284 @Override 1285 int getNativeSize() { 1286 // Return the native allocation needed for the subtree. 1287 int size = NATIVE_ALLOCATION_SIZE; 1288 for (int i = 0; i < mChildren.size(); i++) { 1289 size += mChildren.get(i).getNativeSize(); 1290 } 1291 return size; 1292 } 1293 1294 @Override 1295 public boolean canApplyTheme() { 1296 if (mThemeAttrs != null) { 1297 return true; 1298 } 1299 1300 final ArrayList<VObject> children = mChildren; 1301 for (int i = 0, count = children.size(); i < count; i++) { 1302 final VObject child = children.get(i); 1303 if (child.canApplyTheme()) { 1304 return true; 1305 } 1306 } 1307 1308 return false; 1309 } 1310 1311 @Override 1312 public void applyTheme(Theme t) { 1313 if (mThemeAttrs != null) { 1314 final TypedArray a = t.resolveAttributes(mThemeAttrs, 1315 R.styleable.VectorDrawableGroup); 1316 updateStateFromTypedArray(a); 1317 a.recycle(); 1318 } 1319 1320 final ArrayList<VObject> children = mChildren; 1321 for (int i = 0, count = children.size(); i < count; i++) { 1322 final VObject child = children.get(i); 1323 if (child.canApplyTheme()) { 1324 child.applyTheme(t); 1325 1326 // Applying a theme may have made the child stateful. 1327 mIsStateful |= child.isStateful(); 1328 } 1329 } 1330 } 1331 1332 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 1333 @SuppressWarnings("unused") 1334 public float getRotation() { 1335 return isTreeValid() ? nGetRotation(mNativePtr) : 0; 1336 } 1337 1338 @SuppressWarnings("unused") 1339 public void setRotation(float rotation) { 1340 if (isTreeValid()) { 1341 nSetRotation(mNativePtr, rotation); 1342 } 1343 } 1344 1345 @SuppressWarnings("unused") 1346 public float getPivotX() { 1347 return isTreeValid() ? nGetPivotX(mNativePtr) : 0; 1348 } 1349 1350 @SuppressWarnings("unused") 1351 public void setPivotX(float pivotX) { 1352 if (isTreeValid()) { 1353 nSetPivotX(mNativePtr, pivotX); 1354 } 1355 } 1356 1357 @SuppressWarnings("unused") 1358 public float getPivotY() { 1359 return isTreeValid() ? nGetPivotY(mNativePtr) : 0; 1360 } 1361 1362 @SuppressWarnings("unused") 1363 public void setPivotY(float pivotY) { 1364 if (isTreeValid()) { 1365 nSetPivotY(mNativePtr, pivotY); 1366 } 1367 } 1368 1369 @SuppressWarnings("unused") 1370 public float getScaleX() { 1371 return isTreeValid() ? nGetScaleX(mNativePtr) : 0; 1372 } 1373 1374 @SuppressWarnings("unused") 1375 public void setScaleX(float scaleX) { 1376 if (isTreeValid()) { 1377 nSetScaleX(mNativePtr, scaleX); 1378 } 1379 } 1380 1381 @SuppressWarnings("unused") 1382 public float getScaleY() { 1383 return isTreeValid() ? nGetScaleY(mNativePtr) : 0; 1384 } 1385 1386 @SuppressWarnings("unused") 1387 public void setScaleY(float scaleY) { 1388 if (isTreeValid()) { 1389 nSetScaleY(mNativePtr, scaleY); 1390 } 1391 } 1392 1393 @SuppressWarnings("unused") 1394 public float getTranslateX() { 1395 return isTreeValid() ? nGetTranslateX(mNativePtr) : 0; 1396 } 1397 1398 @SuppressWarnings("unused") 1399 public void setTranslateX(float translateX) { 1400 if (isTreeValid()) { 1401 nSetTranslateX(mNativePtr, translateX); 1402 } 1403 } 1404 1405 @SuppressWarnings("unused") 1406 public float getTranslateY() { 1407 return isTreeValid() ? nGetTranslateY(mNativePtr) : 0; 1408 } 1409 1410 @SuppressWarnings("unused") 1411 public void setTranslateY(float translateY) { 1412 if (isTreeValid()) { 1413 nSetTranslateY(mNativePtr, translateY); 1414 } 1415 } 1416 } 1417 1418 /** 1419 * Common Path information for clip path and normal path. 1420 */ 1421 static abstract class VPath extends VObject { 1422 protected PathParser.PathData mPathData = null; 1423 1424 String mPathName; 1425 @Config int mChangingConfigurations; 1426 1427 private static final Property<VPath, PathParser.PathData> PATH_DATA = 1428 new Property<VPath, PathParser.PathData>(PathParser.PathData.class, "pathData") { 1429 @Override 1430 public void set(VPath object, PathParser.PathData data) { 1431 object.setPathData(data); 1432 } 1433 1434 @Override 1435 public PathParser.PathData get(VPath object) { 1436 return object.getPathData(); 1437 } 1438 }; 1439 1440 Property getProperty(String propertyName) { 1441 if (PATH_DATA.getName().equals(propertyName)) { 1442 return PATH_DATA; 1443 } 1444 // property not found 1445 return null; 1446 } 1447 1448 public VPath() { 1449 // Empty constructor. 1450 } 1451 1452 public VPath(VPath copy) { 1453 mPathName = copy.mPathName; 1454 mChangingConfigurations = copy.mChangingConfigurations; 1455 mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData); 1456 } 1457 1458 public String getPathName() { 1459 return mPathName; 1460 } 1461 1462 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 1463 @SuppressWarnings("unused") 1464 public PathParser.PathData getPathData() { 1465 return mPathData; 1466 } 1467 1468 // TODO: Move the PathEvaluator and this setter and the getter above into native. 1469 @SuppressWarnings("unused") 1470 public void setPathData(PathParser.PathData pathData) { 1471 mPathData.setPathData(pathData); 1472 if (isTreeValid()) { 1473 nSetPathData(getNativePtr(), mPathData.getNativePtr()); 1474 } 1475 } 1476 } 1477 1478 /** 1479 * Clip path, which only has name and pathData. 1480 */ 1481 private static class VClipPath extends VPath { 1482 private final long mNativePtr; 1483 private static final int NATIVE_ALLOCATION_SIZE = 120; 1484 1485 public VClipPath() { 1486 mNativePtr = nCreateClipPath(); 1487 } 1488 1489 public VClipPath(VClipPath copy) { 1490 super(copy); 1491 mNativePtr = nCreateClipPath(copy.mNativePtr); 1492 } 1493 1494 @Override 1495 public long getNativePtr() { 1496 return mNativePtr; 1497 } 1498 1499 @Override 1500 public void inflate(Resources r, AttributeSet attrs, Theme theme) { 1501 final TypedArray a = obtainAttributes(r, theme, attrs, 1502 R.styleable.VectorDrawableClipPath); 1503 updateStateFromTypedArray(a); 1504 a.recycle(); 1505 } 1506 1507 @Override 1508 public boolean canApplyTheme() { 1509 return false; 1510 } 1511 1512 @Override 1513 public void applyTheme(Theme theme) { 1514 // No-op. 1515 } 1516 1517 @Override 1518 public boolean onStateChange(int[] stateSet) { 1519 return false; 1520 } 1521 1522 @Override 1523 public boolean isStateful() { 1524 return false; 1525 } 1526 1527 @Override 1528 int getNativeSize() { 1529 return NATIVE_ALLOCATION_SIZE; 1530 } 1531 1532 private void updateStateFromTypedArray(TypedArray a) { 1533 // Account for any configuration changes. 1534 mChangingConfigurations |= a.getChangingConfigurations(); 1535 1536 final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name); 1537 if (pathName != null) { 1538 mPathName = pathName; 1539 nSetName(mNativePtr, mPathName); 1540 } 1541 1542 final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData); 1543 if (pathDataString != null) { 1544 mPathData = new PathParser.PathData(pathDataString); 1545 nSetPathString(mNativePtr, pathDataString, pathDataString.length()); 1546 } 1547 } 1548 } 1549 1550 /** 1551 * Normal path, which contains all the fill / paint information. 1552 */ 1553 static class VFullPath extends VPath { 1554 private static final int STROKE_WIDTH_INDEX = 0; 1555 private static final int STROKE_COLOR_INDEX = 1; 1556 private static final int STROKE_ALPHA_INDEX = 2; 1557 private static final int FILL_COLOR_INDEX = 3; 1558 private static final int FILL_ALPHA_INDEX = 4; 1559 private static final int TRIM_PATH_START_INDEX = 5; 1560 private static final int TRIM_PATH_END_INDEX = 6; 1561 private static final int TRIM_PATH_OFFSET_INDEX = 7; 1562 private static final int STROKE_LINE_CAP_INDEX = 8; 1563 private static final int STROKE_LINE_JOIN_INDEX = 9; 1564 private static final int STROKE_MITER_LIMIT_INDEX = 10; 1565 private static final int FILL_TYPE_INDEX = 11; 1566 private static final int TOTAL_PROPERTY_COUNT = 12; 1567 1568 private static final int NATIVE_ALLOCATION_SIZE = 264; 1569 // Property map for animatable attributes. 1570 private final static HashMap<String, Integer> sPropertyIndexMap 1571 = new HashMap<String, Integer> () { 1572 { 1573 put("strokeWidth", STROKE_WIDTH_INDEX); 1574 put("strokeColor", STROKE_COLOR_INDEX); 1575 put("strokeAlpha", STROKE_ALPHA_INDEX); 1576 put("fillColor", FILL_COLOR_INDEX); 1577 put("fillAlpha", FILL_ALPHA_INDEX); 1578 put("trimPathStart", TRIM_PATH_START_INDEX); 1579 put("trimPathEnd", TRIM_PATH_END_INDEX); 1580 put("trimPathOffset", TRIM_PATH_OFFSET_INDEX); 1581 } 1582 }; 1583 1584 // Below are the Properties that wrap the setters to avoid reflection overhead in animations 1585 private static final Property<VFullPath, Float> STROKE_WIDTH = 1586 new FloatProperty<VFullPath> ("strokeWidth") { 1587 @Override 1588 public void setValue(VFullPath object, float value) { 1589 object.setStrokeWidth(value); 1590 } 1591 1592 @Override 1593 public Float get(VFullPath object) { 1594 return object.getStrokeWidth(); 1595 } 1596 }; 1597 1598 private static final Property<VFullPath, Integer> STROKE_COLOR = 1599 new IntProperty<VFullPath> ("strokeColor") { 1600 @Override 1601 public void setValue(VFullPath object, int value) { 1602 object.setStrokeColor(value); 1603 } 1604 1605 @Override 1606 public Integer get(VFullPath object) { 1607 return object.getStrokeColor(); 1608 } 1609 }; 1610 1611 private static final Property<VFullPath, Float> STROKE_ALPHA = 1612 new FloatProperty<VFullPath> ("strokeAlpha") { 1613 @Override 1614 public void setValue(VFullPath object, float value) { 1615 object.setStrokeAlpha(value); 1616 } 1617 1618 @Override 1619 public Float get(VFullPath object) { 1620 return object.getStrokeAlpha(); 1621 } 1622 }; 1623 1624 private static final Property<VFullPath, Integer> FILL_COLOR = 1625 new IntProperty<VFullPath>("fillColor") { 1626 @Override 1627 public void setValue(VFullPath object, int value) { 1628 object.setFillColor(value); 1629 } 1630 1631 @Override 1632 public Integer get(VFullPath object) { 1633 return object.getFillColor(); 1634 } 1635 }; 1636 1637 private static final Property<VFullPath, Float> FILL_ALPHA = 1638 new FloatProperty<VFullPath> ("fillAlpha") { 1639 @Override 1640 public void setValue(VFullPath object, float value) { 1641 object.setFillAlpha(value); 1642 } 1643 1644 @Override 1645 public Float get(VFullPath object) { 1646 return object.getFillAlpha(); 1647 } 1648 }; 1649 1650 private static final Property<VFullPath, Float> TRIM_PATH_START = 1651 new FloatProperty<VFullPath> ("trimPathStart") { 1652 @Override 1653 public void setValue(VFullPath object, float value) { 1654 object.setTrimPathStart(value); 1655 } 1656 1657 @Override 1658 public Float get(VFullPath object) { 1659 return object.getTrimPathStart(); 1660 } 1661 }; 1662 1663 private static final Property<VFullPath, Float> TRIM_PATH_END = 1664 new FloatProperty<VFullPath> ("trimPathEnd") { 1665 @Override 1666 public void setValue(VFullPath object, float value) { 1667 object.setTrimPathEnd(value); 1668 } 1669 1670 @Override 1671 public Float get(VFullPath object) { 1672 return object.getTrimPathEnd(); 1673 } 1674 }; 1675 1676 private static final Property<VFullPath, Float> TRIM_PATH_OFFSET = 1677 new FloatProperty<VFullPath> ("trimPathOffset") { 1678 @Override 1679 public void setValue(VFullPath object, float value) { 1680 object.setTrimPathOffset(value); 1681 } 1682 1683 @Override 1684 public Float get(VFullPath object) { 1685 return object.getTrimPathOffset(); 1686 } 1687 }; 1688 1689 private final static HashMap<String, Property> sPropertyMap 1690 = new HashMap<String, Property> () { 1691 { 1692 put("strokeWidth", STROKE_WIDTH); 1693 put("strokeColor", STROKE_COLOR); 1694 put("strokeAlpha", STROKE_ALPHA); 1695 put("fillColor", FILL_COLOR); 1696 put("fillAlpha", FILL_ALPHA); 1697 put("trimPathStart", TRIM_PATH_START); 1698 put("trimPathEnd", TRIM_PATH_END); 1699 put("trimPathOffset", TRIM_PATH_OFFSET); 1700 } 1701 }; 1702 1703 // Temp array to store property data obtained from native getter. 1704 private byte[] mPropertyData; 1705 ///////////////////////////////////////////////////// 1706 // Variables below need to be copied (deep copy if applicable) for mutation. 1707 private int[] mThemeAttrs; 1708 1709 ComplexColor mStrokeColors = null; 1710 ComplexColor mFillColors = null; 1711 private final long mNativePtr; 1712 1713 public VFullPath() { 1714 mNativePtr = nCreateFullPath(); 1715 } 1716 1717 public VFullPath(VFullPath copy) { 1718 super(copy); 1719 mNativePtr = nCreateFullPath(copy.mNativePtr); 1720 mThemeAttrs = copy.mThemeAttrs; 1721 mStrokeColors = copy.mStrokeColors; 1722 mFillColors = copy.mFillColors; 1723 } 1724 1725 Property getProperty(String propertyName) { 1726 Property p = super.getProperty(propertyName); 1727 if (p != null) { 1728 return p; 1729 } 1730 if (sPropertyMap.containsKey(propertyName)) { 1731 return sPropertyMap.get(propertyName); 1732 } else { 1733 // property not found 1734 return null; 1735 } 1736 } 1737 1738 int getPropertyIndex(String propertyName) { 1739 if (!sPropertyIndexMap.containsKey(propertyName)) { 1740 return -1; 1741 } else { 1742 return sPropertyIndexMap.get(propertyName); 1743 } 1744 } 1745 1746 @Override 1747 public boolean onStateChange(int[] stateSet) { 1748 boolean changed = false; 1749 1750 if (mStrokeColors != null && mStrokeColors instanceof ColorStateList) { 1751 final int oldStrokeColor = getStrokeColor(); 1752 final int newStrokeColor = 1753 ((ColorStateList) mStrokeColors).getColorForState(stateSet, oldStrokeColor); 1754 changed |= oldStrokeColor != newStrokeColor; 1755 if (oldStrokeColor != newStrokeColor) { 1756 nSetStrokeColor(mNativePtr, newStrokeColor); 1757 } 1758 } 1759 1760 if (mFillColors != null && mFillColors instanceof ColorStateList) { 1761 final int oldFillColor = getFillColor(); 1762 final int newFillColor = ((ColorStateList) mFillColors).getColorForState(stateSet, oldFillColor); 1763 changed |= oldFillColor != newFillColor; 1764 if (oldFillColor != newFillColor) { 1765 nSetFillColor(mNativePtr, newFillColor); 1766 } 1767 } 1768 1769 return changed; 1770 } 1771 1772 @Override 1773 public boolean isStateful() { 1774 return mStrokeColors != null || mFillColors != null; 1775 } 1776 1777 @Override 1778 int getNativeSize() { 1779 return NATIVE_ALLOCATION_SIZE; 1780 } 1781 1782 @Override 1783 public long getNativePtr() { 1784 return mNativePtr; 1785 } 1786 1787 @Override 1788 public void inflate(Resources r, AttributeSet attrs, Theme theme) { 1789 final TypedArray a = obtainAttributes(r, theme, attrs, 1790 R.styleable.VectorDrawablePath); 1791 updateStateFromTypedArray(a); 1792 a.recycle(); 1793 } 1794 1795 private void updateStateFromTypedArray(TypedArray a) { 1796 int byteCount = TOTAL_PROPERTY_COUNT * 4; 1797 if (mPropertyData == null) { 1798 // Lazy initialization: If the path is created through copy constructor, this may 1799 // never get called. 1800 mPropertyData = new byte[byteCount]; 1801 } 1802 // The bulk getters/setters of property data (e.g. stroke width, color, etc) allows us 1803 // to pull current values from native and store modifications with only two methods, 1804 // minimizing JNI overhead. 1805 boolean success = nGetFullPathProperties(mNativePtr, mPropertyData, byteCount); 1806 if (!success) { 1807 throw new RuntimeException("Error: inconsistent property count"); 1808 } 1809 1810 ByteBuffer properties = ByteBuffer.wrap(mPropertyData); 1811 properties.order(ByteOrder.nativeOrder()); 1812 float strokeWidth = properties.getFloat(STROKE_WIDTH_INDEX * 4); 1813 int strokeColor = properties.getInt(STROKE_COLOR_INDEX * 4); 1814 float strokeAlpha = properties.getFloat(STROKE_ALPHA_INDEX * 4); 1815 int fillColor = properties.getInt(FILL_COLOR_INDEX * 4); 1816 float fillAlpha = properties.getFloat(FILL_ALPHA_INDEX * 4); 1817 float trimPathStart = properties.getFloat(TRIM_PATH_START_INDEX * 4); 1818 float trimPathEnd = properties.getFloat(TRIM_PATH_END_INDEX * 4); 1819 float trimPathOffset = properties.getFloat(TRIM_PATH_OFFSET_INDEX * 4); 1820 int strokeLineCap = properties.getInt(STROKE_LINE_CAP_INDEX * 4); 1821 int strokeLineJoin = properties.getInt(STROKE_LINE_JOIN_INDEX * 4); 1822 float strokeMiterLimit = properties.getFloat(STROKE_MITER_LIMIT_INDEX * 4); 1823 int fillType = properties.getInt(FILL_TYPE_INDEX * 4); 1824 Shader fillGradient = null; 1825 Shader strokeGradient = null; 1826 // Account for any configuration changes. 1827 mChangingConfigurations |= a.getChangingConfigurations(); 1828 1829 // Extract the theme attributes, if any. 1830 mThemeAttrs = a.extractThemeAttrs(); 1831 1832 final String pathName = a.getString(R.styleable.VectorDrawablePath_name); 1833 if (pathName != null) { 1834 mPathName = pathName; 1835 nSetName(mNativePtr, mPathName); 1836 } 1837 1838 final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData); 1839 if (pathString != null) { 1840 mPathData = new PathParser.PathData(pathString); 1841 nSetPathString(mNativePtr, pathString, pathString.length()); 1842 } 1843 1844 final ComplexColor fillColors = a.getComplexColor( 1845 R.styleable.VectorDrawablePath_fillColor); 1846 if (fillColors != null) { 1847 // If the colors is a gradient color, or the color state list is stateful, keep the 1848 // colors information. Otherwise, discard the colors and keep the default color. 1849 if (fillColors instanceof GradientColor) { 1850 mFillColors = fillColors; 1851 fillGradient = ((GradientColor) fillColors).getShader(); 1852 } else if (fillColors.isStateful()) { 1853 mFillColors = fillColors; 1854 } else { 1855 mFillColors = null; 1856 } 1857 fillColor = fillColors.getDefaultColor(); 1858 } 1859 1860 final ComplexColor strokeColors = a.getComplexColor( 1861 R.styleable.VectorDrawablePath_strokeColor); 1862 if (strokeColors != null) { 1863 // If the colors is a gradient color, or the color state list is stateful, keep the 1864 // colors information. Otherwise, discard the colors and keep the default color. 1865 if (strokeColors instanceof GradientColor) { 1866 mStrokeColors = strokeColors; 1867 strokeGradient = ((GradientColor) strokeColors).getShader(); 1868 } else if (strokeColors.isStateful()) { 1869 mStrokeColors = strokeColors; 1870 } else { 1871 mStrokeColors = null; 1872 } 1873 strokeColor = strokeColors.getDefaultColor(); 1874 } 1875 // Update the gradient info, even if the gradiet is null. 1876 nUpdateFullPathFillGradient(mNativePtr, 1877 fillGradient != null ? fillGradient.getNativeInstance() : 0); 1878 nUpdateFullPathStrokeGradient(mNativePtr, 1879 strokeGradient != null ? strokeGradient.getNativeInstance() : 0); 1880 1881 fillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, fillAlpha); 1882 1883 strokeLineCap = a.getInt( 1884 R.styleable.VectorDrawablePath_strokeLineCap, strokeLineCap); 1885 strokeLineJoin = a.getInt( 1886 R.styleable.VectorDrawablePath_strokeLineJoin, strokeLineJoin); 1887 strokeMiterLimit = a.getFloat( 1888 R.styleable.VectorDrawablePath_strokeMiterLimit, strokeMiterLimit); 1889 strokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha, 1890 strokeAlpha); 1891 strokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, 1892 strokeWidth); 1893 trimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, 1894 trimPathEnd); 1895 trimPathOffset = a.getFloat( 1896 R.styleable.VectorDrawablePath_trimPathOffset, trimPathOffset); 1897 trimPathStart = a.getFloat( 1898 R.styleable.VectorDrawablePath_trimPathStart, trimPathStart); 1899 fillType = a.getInt(R.styleable.VectorDrawablePath_fillType, fillType); 1900 1901 nUpdateFullPathProperties(mNativePtr, strokeWidth, strokeColor, strokeAlpha, 1902 fillColor, fillAlpha, trimPathStart, trimPathEnd, trimPathOffset, 1903 strokeMiterLimit, strokeLineCap, strokeLineJoin, fillType); 1904 } 1905 1906 @Override 1907 public boolean canApplyTheme() { 1908 if (mThemeAttrs != null) { 1909 return true; 1910 } 1911 1912 boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors); 1913 boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors); 1914 if (fillCanApplyTheme || strokeCanApplyTheme) { 1915 return true; 1916 } 1917 return false; 1918 1919 } 1920 1921 @Override 1922 public void applyTheme(Theme t) { 1923 // Resolve the theme attributes directly referred by the VectorDrawable. 1924 if (mThemeAttrs != null) { 1925 final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath); 1926 updateStateFromTypedArray(a); 1927 a.recycle(); 1928 } 1929 1930 // Resolve the theme attributes in-directly referred by the VectorDrawable, for example, 1931 // fillColor can refer to a color state list which itself needs to apply theme. 1932 // And this is the reason we still want to keep partial update for the path's properties. 1933 boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors); 1934 boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors); 1935 1936 if (fillCanApplyTheme) { 1937 mFillColors = mFillColors.obtainForTheme(t); 1938 if (mFillColors instanceof GradientColor) { 1939 nUpdateFullPathFillGradient(mNativePtr, 1940 ((GradientColor) mFillColors).getShader().getNativeInstance()); 1941 } else if (mFillColors instanceof ColorStateList) { 1942 nSetFillColor(mNativePtr, mFillColors.getDefaultColor()); 1943 } 1944 } 1945 1946 if (strokeCanApplyTheme) { 1947 mStrokeColors = mStrokeColors.obtainForTheme(t); 1948 if (mStrokeColors instanceof GradientColor) { 1949 nUpdateFullPathStrokeGradient(mNativePtr, 1950 ((GradientColor) mStrokeColors).getShader().getNativeInstance()); 1951 } else if (mStrokeColors instanceof ColorStateList) { 1952 nSetStrokeColor(mNativePtr, mStrokeColors.getDefaultColor()); 1953 } 1954 } 1955 } 1956 1957 private boolean canComplexColorApplyTheme(ComplexColor complexColor) { 1958 return complexColor != null && complexColor.canApplyTheme(); 1959 } 1960 1961 /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ 1962 @SuppressWarnings("unused") 1963 int getStrokeColor() { 1964 return isTreeValid() ? nGetStrokeColor(mNativePtr) : 0; 1965 } 1966 1967 @SuppressWarnings("unused") 1968 void setStrokeColor(int strokeColor) { 1969 mStrokeColors = null; 1970 if (isTreeValid()) { 1971 nSetStrokeColor(mNativePtr, strokeColor); 1972 } 1973 } 1974 1975 @SuppressWarnings("unused") 1976 float getStrokeWidth() { 1977 return isTreeValid() ? nGetStrokeWidth(mNativePtr) : 0; 1978 } 1979 1980 @SuppressWarnings("unused") 1981 void setStrokeWidth(float strokeWidth) { 1982 if (isTreeValid()) { 1983 nSetStrokeWidth(mNativePtr, strokeWidth); 1984 } 1985 } 1986 1987 @SuppressWarnings("unused") 1988 float getStrokeAlpha() { 1989 return isTreeValid() ? nGetStrokeAlpha(mNativePtr) : 0; 1990 } 1991 1992 @SuppressWarnings("unused") 1993 void setStrokeAlpha(float strokeAlpha) { 1994 if (isTreeValid()) { 1995 nSetStrokeAlpha(mNativePtr, strokeAlpha); 1996 } 1997 } 1998 1999 @SuppressWarnings("unused") 2000 int getFillColor() { 2001 return isTreeValid() ? nGetFillColor(mNativePtr) : 0; 2002 } 2003 2004 @SuppressWarnings("unused") 2005 void setFillColor(int fillColor) { 2006 mFillColors = null; 2007 if (isTreeValid()) { 2008 nSetFillColor(mNativePtr, fillColor); 2009 } 2010 } 2011 2012 @SuppressWarnings("unused") 2013 float getFillAlpha() { 2014 return isTreeValid() ? nGetFillAlpha(mNativePtr) : 0; 2015 } 2016 2017 @SuppressWarnings("unused") 2018 void setFillAlpha(float fillAlpha) { 2019 if (isTreeValid()) { 2020 nSetFillAlpha(mNativePtr, fillAlpha); 2021 } 2022 } 2023 2024 @SuppressWarnings("unused") 2025 float getTrimPathStart() { 2026 return isTreeValid() ? nGetTrimPathStart(mNativePtr) : 0; 2027 } 2028 2029 @SuppressWarnings("unused") 2030 void setTrimPathStart(float trimPathStart) { 2031 if (isTreeValid()) { 2032 nSetTrimPathStart(mNativePtr, trimPathStart); 2033 } 2034 } 2035 2036 @SuppressWarnings("unused") 2037 float getTrimPathEnd() { 2038 return isTreeValid() ? nGetTrimPathEnd(mNativePtr) : 0; 2039 } 2040 2041 @SuppressWarnings("unused") 2042 void setTrimPathEnd(float trimPathEnd) { 2043 if (isTreeValid()) { 2044 nSetTrimPathEnd(mNativePtr, trimPathEnd); 2045 } 2046 } 2047 2048 @SuppressWarnings("unused") 2049 float getTrimPathOffset() { 2050 return isTreeValid() ? nGetTrimPathOffset(mNativePtr) : 0; 2051 } 2052 2053 @SuppressWarnings("unused") 2054 void setTrimPathOffset(float trimPathOffset) { 2055 if (isTreeValid()) { 2056 nSetTrimPathOffset(mNativePtr, trimPathOffset); 2057 } 2058 } 2059 } 2060 2061 abstract static class VObject { 2062 VirtualRefBasePtr mTreePtr = null; 2063 boolean isTreeValid() { 2064 return mTreePtr != null && mTreePtr.get() != 0; 2065 } 2066 void setTree(VirtualRefBasePtr ptr) { 2067 mTreePtr = ptr; 2068 } 2069 abstract long getNativePtr(); 2070 abstract void inflate(Resources r, AttributeSet attrs, Theme theme); 2071 abstract boolean canApplyTheme(); 2072 abstract void applyTheme(Theme t); 2073 abstract boolean onStateChange(int[] state); 2074 abstract boolean isStateful(); 2075 abstract int getNativeSize(); 2076 abstract Property getProperty(String propertyName); 2077 } 2078 2079 private static native long nCreateTree(long rootGroupPtr); 2080 private static native long nCreateTreeFromCopy(long treeToCopy, long rootGroupPtr); 2081 private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth, 2082 float viewportHeight); 2083 private static native boolean nSetRootAlpha(long rendererPtr, float alpha); 2084 private static native float nGetRootAlpha(long rendererPtr); 2085 private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching); 2086 2087 private static native int nDraw(long rendererPtr, long canvasWrapperPtr, 2088 long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache); 2089 private static native long nCreateFullPath(); 2090 private static native long nCreateFullPath(long nativeFullPathPtr); 2091 private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties, 2092 int length); 2093 2094 private static native void nUpdateFullPathProperties(long pathPtr, float strokeWidth, 2095 int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart, 2096 float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap, 2097 int strokeLineJoin, int fillType); 2098 private static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr); 2099 private static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr); 2100 2101 private static native long nCreateClipPath(); 2102 private static native long nCreateClipPath(long clipPathPtr); 2103 2104 private static native long nCreateGroup(); 2105 private static native long nCreateGroup(long groupPtr); 2106 private static native void nSetName(long nodePtr, String name); 2107 private static native boolean nGetGroupProperties(long groupPtr, float[] properties, 2108 int length); 2109 private static native void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX, 2110 float pivotY, float scaleX, float scaleY, float translateX, float translateY); 2111 2112 private static native void nAddChild(long groupPtr, long nodePtr); 2113 private static native void nSetPathString(long pathPtr, String pathString, int length); 2114 2115 /** 2116 * The setters and getters below for paths and groups are here temporarily, and will be 2117 * removed once the animation in AVD is replaced with RenderNodeAnimator, in which case the 2118 * animation will modify these properties in native. By then no JNI hopping would be necessary 2119 * for VD during animation, and these setters and getters will be obsolete. 2120 */ 2121 // Setters and getters during animation. 2122 private static native float nGetRotation(long groupPtr); 2123 private static native void nSetRotation(long groupPtr, float rotation); 2124 private static native float nGetPivotX(long groupPtr); 2125 private static native void nSetPivotX(long groupPtr, float pivotX); 2126 private static native float nGetPivotY(long groupPtr); 2127 private static native void nSetPivotY(long groupPtr, float pivotY); 2128 private static native float nGetScaleX(long groupPtr); 2129 private static native void nSetScaleX(long groupPtr, float scaleX); 2130 private static native float nGetScaleY(long groupPtr); 2131 private static native void nSetScaleY(long groupPtr, float scaleY); 2132 private static native float nGetTranslateX(long groupPtr); 2133 private static native void nSetTranslateX(long groupPtr, float translateX); 2134 private static native float nGetTranslateY(long groupPtr); 2135 private static native void nSetTranslateY(long groupPtr, float translateY); 2136 2137 // Setters and getters for VPath during animation. 2138 private static native void nSetPathData(long pathPtr, long pathDataPtr); 2139 private static native float nGetStrokeWidth(long pathPtr); 2140 private static native void nSetStrokeWidth(long pathPtr, float width); 2141 private static native int nGetStrokeColor(long pathPtr); 2142 private static native void nSetStrokeColor(long pathPtr, int strokeColor); 2143 private static native float nGetStrokeAlpha(long pathPtr); 2144 private static native void nSetStrokeAlpha(long pathPtr, float alpha); 2145 private static native int nGetFillColor(long pathPtr); 2146 private static native void nSetFillColor(long pathPtr, int fillColor); 2147 private static native float nGetFillAlpha(long pathPtr); 2148 private static native void nSetFillAlpha(long pathPtr, float fillAlpha); 2149 private static native float nGetTrimPathStart(long pathPtr); 2150 private static native void nSetTrimPathStart(long pathPtr, float trimPathStart); 2151 private static native float nGetTrimPathEnd(long pathPtr); 2152 private static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd); 2153 private static native float nGetTrimPathOffset(long pathPtr); 2154 private static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset); 2155} 2156