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