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