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