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