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