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