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