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