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