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