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