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