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