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