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