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