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