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