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