Files
frameworks_base/graphics/java/android/graphics/drawable/VectorDrawable.java
Doris Liu 4bbc293126 VectorDrawable native rendering - Step 3 of MANY
- Refactored VPathRenderer & VectorDrawableState
- Moved all the VD rendering into native
- Set up hooks for VD's property changes in JNI for animated VD

TODO: JNI calls can be further reduced when we convert the animation
in AVD to use RenderNodeAnimator, in which case animation will be
driven from native and therefore most of the JNI hooks for changing
VD's properties during animation will no longer be needed.

Change-Id: I52021f4d7bea057b83ace54085d870dd45acae0f
2015-12-28 16:08:38 -08:00

1620 lines
60 KiB
Java

/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package android.graphics.drawable;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.PorterDuff.Mode;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.LayoutDirection;
import android.util.Log;
import android.util.PathParser;
import android.util.Xml;
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Stack;
/**
* This lets you create a drawable based on an XML vector graphic. It can be
* defined in an XML file with the <code>&lt;vector></code> element.
* <p/>
* The vector drawable has the following elements:
* <p/>
* <dt><code>&lt;vector></code></dt>
* <dl>
* <dd>Used to define a vector drawable
* <dl>
* <dt><code>android:name</code></dt>
* <dd>Defines the name of this vector drawable.</dd>
* <dt><code>android:width</code></dt>
* <dd>Used to define the intrinsic width of the drawable.
* This support all the dimension units, normally specified with dp.</dd>
* <dt><code>android:height</code></dt>
* <dd>Used to define the intrinsic height the drawable.
* This support all the dimension units, normally specified with dp.</dd>
* <dt><code>android:viewportWidth</code></dt>
* <dd>Used to define the width of the viewport space. Viewport is basically
* the virtual canvas where the paths are drawn on.</dd>
* <dt><code>android:viewportHeight</code></dt>
* <dd>Used to define the height of the viewport space. Viewport is basically
* the virtual canvas where the paths are drawn on.</dd>
* <dt><code>android:tint</code></dt>
* <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd>
* <dt><code>android:tintMode</code></dt>
* <dd>The Porter-Duff blending mode for the tint color. The default value is src_in.</dd>
* <dt><code>android:autoMirrored</code></dt>
* <dd>Indicates if the drawable needs to be mirrored when its layout direction is
* RTL (right-to-left).</dd>
* <dt><code>android:alpha</code></dt>
* <dd>The opacity of this drawable.</dd>
* </dl></dd>
* </dl>
*
* <dl>
* <dt><code>&lt;group></code></dt>
* <dd>Defines a group of paths or subgroups, plus transformation information.
* The transformations are defined in the same coordinates as the viewport.
* And the transformations are applied in the order of scale, rotate then translate.
* <dl>
* <dt><code>android:name</code></dt>
* <dd>Defines the name of the group.</dd>
* <dt><code>android:rotation</code></dt>
* <dd>The degrees of rotation of the group.</dd>
* <dt><code>android:pivotX</code></dt>
* <dd>The X coordinate of the pivot for the scale and rotation of the group.
* This is defined in the viewport space.</dd>
* <dt><code>android:pivotY</code></dt>
* <dd>The Y coordinate of the pivot for the scale and rotation of the group.
* This is defined in the viewport space.</dd>
* <dt><code>android:scaleX</code></dt>
* <dd>The amount of scale on the X Coordinate.</dd>
* <dt><code>android:scaleY</code></dt>
* <dd>The amount of scale on the Y coordinate.</dd>
* <dt><code>android:translateX</code></dt>
* <dd>The amount of translation on the X coordinate.
* This is defined in the viewport space.</dd>
* <dt><code>android:translateY</code></dt>
* <dd>The amount of translation on the Y coordinate.
* This is defined in the viewport space.</dd>
* </dl></dd>
* </dl>
*
* <dl>
* <dt><code>&lt;path></code></dt>
* <dd>Defines paths to be drawn.
* <dl>
* <dt><code>android:name</code></dt>
* <dd>Defines the name of the path.</dd>
* <dt><code>android:pathData</code></dt>
* <dd>Defines path data using exactly same format as "d" attribute
* in the SVG's path data. This is defined in the viewport space.</dd>
* <dt><code>android:fillColor</code></dt>
* <dd>Specifies the color used to fill the path. May be a color or (SDK 24+ only) a color state
* list. If this property is animated, any value set by the animation will override the original
* value. No path fill is drawn if this property is not specified.</dd>
* <dt><code>android:strokeColor</code></dt>
* <dd>Specifies the color used to draw the path outline. May be a color or (SDK 24+ only) a color
* state list. If this property is animated, any value set by the animation will override the
* original value. No path outline is drawn if this property is not specified.</dd>
* <dt><code>android:strokeWidth</code></dt>
* <dd>The width a path stroke.</dd>
* <dt><code>android:strokeAlpha</code></dt>
* <dd>The opacity of a path stroke.</dd>
* <dt><code>android:fillAlpha</code></dt>
* <dd>The opacity to fill the path with.</dd>
* <dt><code>android:trimPathStart</code></dt>
* <dd>The fraction of the path to trim from the start, in the range from 0 to 1.</dd>
* <dt><code>android:trimPathEnd</code></dt>
* <dd>The fraction of the path to trim from the end, in the range from 0 to 1.</dd>
* <dt><code>android:trimPathOffset</code></dt>
* <dd>Shift trim region (allows showed region to include the start and end), in the range
* from 0 to 1.</dd>
* <dt><code>android:strokeLineCap</code></dt>
* <dd>Sets the linecap for a stroked path: butt, round, square.</dd>
* <dt><code>android:strokeLineJoin</code></dt>
* <dd>Sets the lineJoin for a stroked path: miter,round,bevel.</dd>
* <dt><code>android:strokeMiterLimit</code></dt>
* <dd>Sets the Miter limit for a stroked path.</dd>
* </dl></dd>
* </dl>
*
* <dl>
* <dt><code>&lt;clip-path></code></dt>
* <dd>Defines path to be the current clip. Note that the clip path only apply to
* the current group and its children.
* <dl>
* <dt><code>android:name</code></dt>
* <dd>Defines the name of the clip path.</dd>
* <dt><code>android:pathData</code></dt>
* <dd>Defines clip path using the same format as "d" attribute
* in the SVG's path data.</dd>
* </dl></dd>
* </dl>
* <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
* <pre>
* &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
* android:height=&quot;64dp&quot;
* android:width=&quot;64dp&quot;
* android:viewportHeight=&quot;600&quot;
* android:viewportWidth=&quot;600&quot; &gt;
* &lt;group
* android:name=&quot;rotationGroup&quot;
* android:pivotX=&quot;300.0&quot;
* android:pivotY=&quot;300.0&quot;
* android:rotation=&quot;45.0&quot; &gt;
* &lt;path
* android:name=&quot;v&quot;
* android:fillColor=&quot;#000000&quot;
* android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
* &lt;/group&gt;
* &lt;/vector&gt;
* </pre></li>
*/
public class VectorDrawable extends Drawable {
private static final String LOGTAG = VectorDrawable.class.getSimpleName();
private static final String SHAPE_CLIP_PATH = "clip-path";
private static final String SHAPE_GROUP = "group";
private static final String SHAPE_PATH = "path";
private static final String SHAPE_VECTOR = "vector";
private VectorDrawableState mVectorState;
private PorterDuffColorFilter mTintFilter;
private ColorFilter mColorFilter;
private boolean mMutated;
/** The density of the display on which this drawable will be rendered. */
private int mTargetDensity;
// Given the virtual display setup, the dpi can be different than the inflation's dpi.
// Therefore, we need to scale the values we got from the getDimension*().
private int mDpiScaledWidth = 0;
private int mDpiScaledHeight = 0;
private Insets mDpiScaledInsets = Insets.NONE;
/** Whether DPI-scaled width, height, and insets need to be updated. */
private boolean mDpiScaledDirty = true;
// Temp variable, only for saving "new" operation at the draw() time.
private final Rect mTmpBounds = new Rect();
public VectorDrawable() {
this(new VectorDrawableState(), null);
}
/**
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
private VectorDrawable(@NonNull VectorDrawableState state, @Nullable Resources res) {
mVectorState = state;
updateLocalState(res);
}
/**
* Initializes local dynamic properties from state. This should be called
* after significant state changes, e.g. from the One True Constructor and
* after inflating or applying a theme.
*
* @param res resources of the context in which the drawable will be
* displayed, or {@code null} to use the constant state defaults
*/
private void updateLocalState(Resources res) {
final int density = Drawable.resolveDensity(res, mVectorState.mDensity);
if (mTargetDensity != density) {
mTargetDensity = density;
mDpiScaledDirty = true;
}
mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
mVectorState = new VectorDrawableState(mVectorState);
mMutated = true;
}
return this;
}
/**
* @hide
*/
public void clearMutated() {
super.clearMutated();
mMutated = false;
}
Object getTargetByName(String name) {
return mVectorState.mVGTargetsMap.get(name);
}
@Override
public ConstantState getConstantState() {
mVectorState.mChangingConfigurations = getChangingConfigurations();
return mVectorState;
}
@Override
public void draw(Canvas canvas) {
// We will offset the bounds for drawBitmap, so copyBounds() here instead
// of getBounds().
copyBounds(mTmpBounds);
if (mTmpBounds.width() <= 0 || mTmpBounds.height() <= 0) {
// Nothing to draw
return;
}
// Color filters always override tint filters.
final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter);
final long colorFilterNativeInstance = colorFilter == null ? 0 :
colorFilter.native_instance;
boolean canReuseCache = mVectorState.canReuseCache();
nDraw(mVectorState.getNativeRenderer(), canvas.getNativeCanvasWrapper(),
colorFilterNativeInstance, mTmpBounds, needMirroring(),
canReuseCache);
}
@Override
public int getAlpha() {
return (int) (mVectorState.getAlpha() * 255);
}
@Override
public void setAlpha(int alpha) {
if (mVectorState.setAlpha(alpha / 255f)) {
invalidateSelf();
}
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mColorFilter = colorFilter;
invalidateSelf();
}
@Override
public ColorFilter getColorFilter() {
return mColorFilter;
}
@Override
public void setTintList(ColorStateList tint) {
final VectorDrawableState state = mVectorState;
if (state.mTint != tint) {
state.mTint = tint;
mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode);
invalidateSelf();
}
}
@Override
public void setTintMode(Mode tintMode) {
final VectorDrawableState state = mVectorState;
if (state.mTintMode != tintMode) {
state.mTintMode = tintMode;
mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode);
invalidateSelf();
}
}
@Override
public boolean isStateful() {
return super.isStateful() || (mVectorState != null && mVectorState.isStateful());
}
@Override
protected boolean onStateChange(int[] stateSet) {
boolean changed = false;
final VectorDrawableState state = mVectorState;
if (state.onStateChange(stateSet)) {
changed = true;
state.mCacheDirty = true;
}
if (state.mTint != null && state.mTintMode != null) {
mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
changed = true;
}
return changed;
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth() {
if (mDpiScaledDirty) {
computeVectorSize();
}
return mDpiScaledWidth;
}
@Override
public int getIntrinsicHeight() {
if (mDpiScaledDirty) {
computeVectorSize();
}
return mDpiScaledHeight;
}
/** @hide */
@Override
public Insets getOpticalInsets() {
if (mDpiScaledDirty) {
computeVectorSize();
}
return mDpiScaledInsets;
}
/*
* Update local dimensions to adjust for a target density that may differ
* from the source density against which the constant state was loaded.
*/
void computeVectorSize() {
final Insets opticalInsets = mVectorState.mOpticalInsets;
final int sourceDensity = mVectorState.mDensity;
final int targetDensity = mTargetDensity;
if (targetDensity != sourceDensity) {
mDpiScaledWidth = Drawable.scaleFromDensity(
(int) mVectorState.mBaseWidth, sourceDensity, targetDensity, true);
mDpiScaledHeight = Drawable.scaleFromDensity(
(int) mVectorState.mBaseHeight,sourceDensity, targetDensity, true);
final int left = Drawable.scaleFromDensity(
opticalInsets.left, sourceDensity, targetDensity, false);
final int right = Drawable.scaleFromDensity(
opticalInsets.right, sourceDensity, targetDensity, false);
final int top = Drawable.scaleFromDensity(
opticalInsets.top, sourceDensity, targetDensity, false);
final int bottom = Drawable.scaleFromDensity(
opticalInsets.bottom, sourceDensity, targetDensity, false);
mDpiScaledInsets = Insets.of(left, top, right, bottom);
} else {
mDpiScaledWidth = (int) mVectorState.mBaseWidth;
mDpiScaledHeight = (int) mVectorState.mBaseHeight;
mDpiScaledInsets = opticalInsets;
}
mDpiScaledDirty = false;
}
@Override
public boolean canApplyTheme() {
return (mVectorState != null && mVectorState.canApplyTheme()) || super.canApplyTheme();
}
@Override
public void applyTheme(Theme t) {
super.applyTheme(t);
final VectorDrawableState state = mVectorState;
if (state == null) {
return;
}
final boolean changedDensity = mVectorState.setDensity(
Drawable.resolveDensity(t.getResources(), 0));
mDpiScaledDirty |= changedDensity;
if (state.mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(
state.mThemeAttrs, R.styleable.VectorDrawable);
try {
state.mCacheDirty = true;
updateStateFromTypedArray(a);
} catch (XmlPullParserException e) {
throw new RuntimeException(e);
} finally {
a.recycle();
}
// May have changed size.
mDpiScaledDirty = true;
}
// Apply theme to contained color state list.
if (state.mTint != null && state.mTint.canApplyTheme()) {
state.mTint = state.mTint.obtainForTheme(t);
}
if (mVectorState != null && mVectorState.canApplyTheme()) {
mVectorState.applyTheme(t);
}
// Update local properties.
updateLocalState(t.getResources());
}
/**
* The size of a pixel when scaled from the intrinsic dimension to the viewport dimension.
* This is used to calculate the path animation accuracy.
*
* @hide
*/
public float getPixelSize() {
if (mVectorState == null ||
mVectorState.mBaseWidth == 0 ||
mVectorState.mBaseHeight == 0 ||
mVectorState.mViewportHeight == 0 ||
mVectorState.mViewportWidth == 0) {
return 1; // fall back to 1:1 pixel mapping.
}
float intrinsicWidth = mVectorState.mBaseWidth;
float intrinsicHeight = mVectorState.mBaseHeight;
float viewportWidth = mVectorState.mViewportWidth;
float viewportHeight = mVectorState.mViewportHeight;
float scaleX = viewportWidth / intrinsicWidth;
float scaleY = viewportHeight / intrinsicHeight;
return Math.min(scaleX, scaleY);
}
/** @hide */
public static VectorDrawable create(Resources resources, int rid) {
try {
final XmlPullParser parser = resources.getXml(rid);
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
while ((type=parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty loop
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
final VectorDrawable drawable = new VectorDrawable();
drawable.inflate(resources, parser, attrs);
return drawable;
} catch (XmlPullParserException e) {
Log.e(LOGTAG, "parser error", e);
} catch (IOException e) {
Log.e(LOGTAG, "parser error", e);
}
return null;
}
@Override
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererPtr != 0) {
// This VD has been used to display other VD resource content, clean up.
mVectorState.mRootGroup = new VGroup();
if (mVectorState.mNativeRendererPtr != 0) {
nDestroyRenderer(mVectorState.mNativeRendererPtr);
}
mVectorState.mNativeRendererPtr = nCreateRenderer(mVectorState.mRootGroup.mNativePtr);
}
final VectorDrawableState state = mVectorState;
state.setDensity(Drawable.resolveDensity(r, 0));
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
updateStateFromTypedArray(a);
a.recycle();
mDpiScaledDirty = true;
state.mCacheDirty = true;
inflateChildElements(r, parser, attrs, theme);
// Update local properties.
updateLocalState(r);
}
private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
final VectorDrawableState state = mVectorState;
// Account for any configuration changes.
state.mChangingConfigurations |= a.getChangingConfigurations();
// Extract the theme attributes, if any.
state.mThemeAttrs = a.extractThemeAttrs();
final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
if (tintMode != -1) {
state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
}
final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
if (tint != null) {
state.mTint = tint;
}
state.mAutoMirrored = a.getBoolean(
R.styleable.VectorDrawable_autoMirrored, state.mAutoMirrored);
float viewportWidth = a.getFloat(
R.styleable.VectorDrawable_viewportWidth, state.mViewportWidth);
float viewportHeight = a.getFloat(
R.styleable.VectorDrawable_viewportHeight, state.mViewportHeight);
state.setViewportSize(viewportWidth, viewportHeight);
if (state.mViewportWidth <= 0) {
throw new XmlPullParserException(a.getPositionDescription() +
"<vector> tag requires viewportWidth > 0");
} else if (state.mViewportHeight <= 0) {
throw new XmlPullParserException(a.getPositionDescription() +
"<vector> tag requires viewportHeight > 0");
}
state.mBaseWidth = a.getDimension(
R.styleable.VectorDrawable_width, state.mBaseWidth);
state.mBaseHeight = a.getDimension(
R.styleable.VectorDrawable_height, state.mBaseHeight);
if (state.mBaseWidth <= 0) {
throw new XmlPullParserException(a.getPositionDescription() +
"<vector> tag requires width > 0");
} else if (state.mBaseHeight <= 0) {
throw new XmlPullParserException(a.getPositionDescription() +
"<vector> tag requires height > 0");
}
final int insetLeft = a.getDimensionPixelOffset(
R.styleable.VectorDrawable_opticalInsetLeft, state.mOpticalInsets.left);
final int insetTop = a.getDimensionPixelOffset(
R.styleable.VectorDrawable_opticalInsetTop, state.mOpticalInsets.top);
final int insetRight = a.getDimensionPixelOffset(
R.styleable.VectorDrawable_opticalInsetRight, state.mOpticalInsets.right);
final int insetBottom = a.getDimensionPixelOffset(
R.styleable.VectorDrawable_opticalInsetBottom, state.mOpticalInsets.bottom);
state.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
final float alphaInFloat = a.getFloat(
R.styleable.VectorDrawable_alpha, state.getAlpha());
state.setAlpha(alphaInFloat);
final String name = a.getString(R.styleable.VectorDrawable_name);
if (name != null) {
state.mRootName = name;
state.mVGTargetsMap.put(name, state);
}
}
private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
final VectorDrawableState state = mVectorState;
boolean noPathTag = true;
// Use a stack to help to build the group tree.
// The top of the stack is always the current group.
final Stack<VGroup> groupStack = new Stack<VGroup>();
groupStack.push(state.mRootGroup);
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
final String tagName = parser.getName();
final VGroup currentGroup = groupStack.peek();
if (SHAPE_PATH.equals(tagName)) {
final VFullPath path = new VFullPath();
path.inflate(res, attrs, theme);
currentGroup.addChild(path);
if (path.getPathName() != null) {
state.mVGTargetsMap.put(path.getPathName(), path);
}
noPathTag = false;
state.mChangingConfigurations |= path.mChangingConfigurations;
} else if (SHAPE_CLIP_PATH.equals(tagName)) {
final VClipPath path = new VClipPath();
path.inflate(res, attrs, theme);
currentGroup.addChild(path);
if (path.getPathName() != null) {
state.mVGTargetsMap.put(path.getPathName(), path);
}
state.mChangingConfigurations |= path.mChangingConfigurations;
} else if (SHAPE_GROUP.equals(tagName)) {
VGroup newChildGroup = new VGroup();
newChildGroup.inflate(res, attrs, theme);
currentGroup.addChild(newChildGroup);
groupStack.push(newChildGroup);
if (newChildGroup.getGroupName() != null) {
state.mVGTargetsMap.put(newChildGroup.getGroupName(),
newChildGroup);
}
state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
}
} else if (eventType == XmlPullParser.END_TAG) {
final String tagName = parser.getName();
if (SHAPE_GROUP.equals(tagName)) {
groupStack.pop();
}
}
eventType = parser.next();
}
if (noPathTag) {
final StringBuffer tag = new StringBuffer();
if (tag.length() > 0) {
tag.append(" or ");
}
tag.append(SHAPE_PATH);
throw new XmlPullParserException("no " + tag + " defined");
}
}
@Override
public int getChangingConfigurations() {
return super.getChangingConfigurations() | mVectorState.getChangingConfigurations();
}
void setAllowCaching(boolean allowCaching) {
nSetAllowCaching(mVectorState.getNativeRenderer(), allowCaching);
}
private boolean needMirroring() {
return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
}
@Override
public void setAutoMirrored(boolean mirrored) {
if (mVectorState.mAutoMirrored != mirrored) {
mVectorState.mAutoMirrored = mirrored;
invalidateSelf();
}
}
@Override
public boolean isAutoMirrored() {
return mVectorState.mAutoMirrored;
}
private static class VectorDrawableState extends ConstantState {
// Variables below need to be copied (deep copy if applicable) for mutation.
int[] mThemeAttrs;
int mChangingConfigurations;
ColorStateList mTint = null;
Mode mTintMode = DEFAULT_TINT_MODE;
boolean mAutoMirrored;
float mBaseWidth = 0;
float mBaseHeight = 0;
float mViewportWidth = 0;
float mViewportHeight = 0;
Insets mOpticalInsets = Insets.NONE;
String mRootName = null;
VGroup mRootGroup;
long mNativeRendererPtr;
int mDensity = DisplayMetrics.DENSITY_DEFAULT;
final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
// Fields for cache
int[] mCachedThemeAttrs;
ColorStateList mCachedTint;
Mode mCachedTintMode;
boolean mCachedAutoMirrored;
boolean mCacheDirty;
// Deep copy for mutate() or implicitly mutate.
public VectorDrawableState(VectorDrawableState copy) {
if (copy != null) {
mThemeAttrs = copy.mThemeAttrs;
mChangingConfigurations = copy.mChangingConfigurations;
mTint = copy.mTint;
mTintMode = copy.mTintMode;
mAutoMirrored = copy.mAutoMirrored;
mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
mNativeRendererPtr = nCreateRenderer(mRootGroup.mNativePtr);
mBaseWidth = copy.mBaseWidth;
mBaseHeight = copy.mBaseHeight;
setViewportSize(copy.mViewportWidth, copy.mViewportHeight);
mOpticalInsets = copy.mOpticalInsets;
mRootName = copy.mRootName;
mDensity = copy.mDensity;
if (copy.mRootName != null) {
mVGTargetsMap.put(copy.mRootName, this);
}
}
}
@Override
public void finalize() throws Throwable {
if (mNativeRendererPtr != 0) {
nDestroyRenderer(mNativeRendererPtr);
mNativeRendererPtr = 0;
}
super.finalize();
}
long getNativeRenderer() {
return mNativeRendererPtr;
}
public boolean canReuseCache() {
if (!mCacheDirty
&& mCachedThemeAttrs == mThemeAttrs
&& mCachedTint == mTint
&& mCachedTintMode == mTintMode
&& mCachedAutoMirrored == mAutoMirrored) {
return true;
}
updateCacheStates();
return false;
}
public void updateCacheStates() {
// Use shallow copy here and shallow comparison in canReuseCache(),
// likely hit cache miss more, but practically not much difference.
mCachedThemeAttrs = mThemeAttrs;
mCachedTint = mTint;
mCachedTintMode = mTintMode;
mCachedAutoMirrored = mAutoMirrored;
mCacheDirty = false;
}
public void applyTheme(Theme t) {
mRootGroup.applyTheme(t);
}
@Override
public boolean canApplyTheme() {
return mThemeAttrs != null
|| (mRootGroup != null && mRootGroup.canApplyTheme())
|| (mTint != null && mTint.canApplyTheme())
|| super.canApplyTheme();
}
public VectorDrawableState() {
mRootGroup = new VGroup();
mNativeRendererPtr = nCreateRenderer(mRootGroup.mNativePtr);
}
@Override
public Drawable newDrawable() {
return new VectorDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
return new VectorDrawable(this, res);
}
@Override
public int getChangingConfigurations() {
return mChangingConfigurations
| (mTint != null ? mTint.getChangingConfigurations() : 0);
}
public boolean isStateful() {
return (mTint != null && mTint.isStateful())
|| (mRootGroup != null && mRootGroup.isStateful());
}
void setViewportSize(float viewportWidth, float viewportHeight) {
mViewportWidth = viewportWidth;
mViewportHeight = viewportHeight;
nSetRendererViewportSize(getNativeRenderer(), viewportWidth, viewportHeight);
}
public final boolean setDensity(int targetDensity) {
if (mDensity != targetDensity) {
final int sourceDensity = mDensity;
mDensity = targetDensity;
applyDensityScaling(sourceDensity, targetDensity);
return true;
}
return false;
}
private void applyDensityScaling(int sourceDensity, int targetDensity) {
mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity);
mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity);
final int insetLeft = Drawable.scaleFromDensity(
mOpticalInsets.left, sourceDensity, targetDensity, false);
final int insetTop = Drawable.scaleFromDensity(
mOpticalInsets.top, sourceDensity, targetDensity, false);
final int insetRight = Drawable.scaleFromDensity(
mOpticalInsets.right, sourceDensity, targetDensity, false);
final int insetBottom = Drawable.scaleFromDensity(
mOpticalInsets.bottom, sourceDensity, targetDensity, false);
mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
}
public boolean onStateChange(int[] stateSet) {
return mRootGroup.onStateChange(stateSet);
}
/**
* setAlpha() and getAlpha() are used mostly for animation purpose. Return true if alpha
* has changed.
*/
public boolean setAlpha(float alpha) {
return nSetRootAlpha(mNativeRendererPtr, alpha);
}
@SuppressWarnings("unused")
public float getAlpha() {
return nGetRootAlpha(mNativeRendererPtr);
}
}
private static class VGroup implements VObject {
private static final int ROTATE_INDEX = 0;
private static final int PIVOT_X_INDEX = 1;
private static final int PIVOT_Y_INDEX = 2;
private static final int SCALE_X_INDEX = 3;
private static final int SCALE_Y_INDEX = 4;
private static final int TRANSLATE_X_INDEX = 5;
private static final int TRANSLATE_Y_INDEX = 6;
private static final int TRANSFORM_PROPERTY_COUNT = 7;
// Temp array to store transform values obtained from native.
private float[] mTransform;
/////////////////////////////////////////////////////
// Variables below need to be copied (deep copy if applicable) for mutation.
private final ArrayList<VObject> mChildren = new ArrayList<>();
private boolean mIsStateful;
// mLocalMatrix is updated based on the update of transformation information,
// either parsed from the XML or by animation.
private int mChangingConfigurations;
private int[] mThemeAttrs;
private String mGroupName = null;
private long mNativePtr = 0;
public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
mIsStateful = copy.mIsStateful;
mThemeAttrs = copy.mThemeAttrs;
mGroupName = copy.mGroupName;
mChangingConfigurations = copy.mChangingConfigurations;
if (mGroupName != null) {
targetsMap.put(mGroupName, this);
}
mNativePtr = nCreateGroup(copy.mNativePtr);
final ArrayList<VObject> children = copy.mChildren;
for (int i = 0; i < children.size(); i++) {
final VObject copyChild = children.get(i);
if (copyChild instanceof VGroup) {
final VGroup copyGroup = (VGroup) copyChild;
addChild(new VGroup(copyGroup, targetsMap));
} else {
final VPath newPath;
if (copyChild instanceof VFullPath) {
newPath = new VFullPath((VFullPath) copyChild);
} else if (copyChild instanceof VClipPath) {
newPath = new VClipPath((VClipPath) copyChild);
} else {
throw new IllegalStateException("Unknown object in the tree!");
}
addChild(newPath);
if (newPath.mPathName != null) {
targetsMap.put(newPath.mPathName, newPath);
}
}
}
}
public VGroup() {
mNativePtr = nCreateGroup();
}
public String getGroupName() {
return mGroupName;
}
public void addChild(VObject child) {
nAddChild(mNativePtr, child.getNativePtr());
mChildren.add(child);
mIsStateful |= child.isStateful();
}
@Override
public long getNativePtr() {
return mNativePtr;
}
@Override
public void inflate(Resources res, AttributeSet attrs, Theme theme) {
final TypedArray a = obtainAttributes(res, theme, attrs,
R.styleable.VectorDrawableGroup);
updateStateFromTypedArray(a);
a.recycle();
}
void updateStateFromTypedArray(TypedArray a) {
// Account for any configuration changes.
mChangingConfigurations |= a.getChangingConfigurations();
// Extract the theme attributes, if any.
mThemeAttrs = a.extractThemeAttrs();
if (mTransform == null) {
// Lazy initialization: If the group is created through copy constructor, this may
// never get called.
mTransform = new float[TRANSFORM_PROPERTY_COUNT];
}
boolean success = nGetGroupProperties(mNativePtr, mTransform, TRANSFORM_PROPERTY_COUNT);
if (!success) {
throw new RuntimeException("Error: inconsistent property count");
}
float rotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation,
mTransform[ROTATE_INDEX]);
float pivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX,
mTransform[PIVOT_X_INDEX]);
float pivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY,
mTransform[PIVOT_Y_INDEX]);
float scaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX,
mTransform[SCALE_X_INDEX]);
float scaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY,
mTransform[SCALE_Y_INDEX]);
float translateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX,
mTransform[TRANSLATE_X_INDEX]);
float translateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY,
mTransform[TRANSLATE_Y_INDEX]);
final String groupName = a.getString(R.styleable.VectorDrawableGroup_name);
if (groupName != null) {
mGroupName = groupName;
nSetName(mNativePtr, mGroupName);
}
nUpdateGroupProperties(mNativePtr, rotate, pivotX, pivotY, scaleX, scaleY,
translateX, translateY);
}
@Override
public boolean onStateChange(int[] stateSet) {
boolean changed = false;
final ArrayList<VObject> children = mChildren;
for (int i = 0, count = children.size(); i < count; i++) {
final VObject child = children.get(i);
if (child.isStateful()) {
changed |= child.onStateChange(stateSet);
}
}
return changed;
}
@Override
public boolean isStateful() {
return mIsStateful;
}
@Override
public boolean canApplyTheme() {
if (mThemeAttrs != null) {
return true;
}
final ArrayList<VObject> children = mChildren;
for (int i = 0, count = children.size(); i < count; i++) {
final VObject child = children.get(i);
if (child.canApplyTheme()) {
return true;
}
}
return false;
}
@Override
protected void finalize() throws Throwable {
if (mNativePtr != 0) {
nDestroy(mNativePtr);
mNativePtr = 0;
}
super.finalize();
}
@Override
public void applyTheme(Theme t) {
if (mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(mThemeAttrs,
R.styleable.VectorDrawableGroup);
updateStateFromTypedArray(a);
a.recycle();
}
final ArrayList<VObject> children = mChildren;
for (int i = 0, count = children.size(); i < count; i++) {
final VObject child = children.get(i);
if (child.canApplyTheme()) {
child.applyTheme(t);
// Applying a theme may have made the child stateful.
mIsStateful |= child.isStateful();
}
}
}
/* Setters and Getters, used by animator from AnimatedVectorDrawable. */
@SuppressWarnings("unused")
public float getRotation() {
return nGetRotation(mNativePtr);
}
@SuppressWarnings("unused")
public void setRotation(float rotation) {
nSetRotation(mNativePtr, rotation);
}
@SuppressWarnings("unused")
public float getPivotX() {
return nGetPivotX(mNativePtr);
}
@SuppressWarnings("unused")
public void setPivotX(float pivotX) {
nSetPivotX(mNativePtr, pivotX);
}
@SuppressWarnings("unused")
public float getPivotY() {
return nGetPivotY(mNativePtr);
}
@SuppressWarnings("unused")
public void setPivotY(float pivotY) {
nSetPivotY(mNativePtr, pivotY);
}
@SuppressWarnings("unused")
public float getScaleX() {
return nGetScaleX(mNativePtr);
}
@SuppressWarnings("unused")
public void setScaleX(float scaleX) {
nSetScaleX(mNativePtr, scaleX);
}
@SuppressWarnings("unused")
public float getScaleY() {
return nGetScaleY(mNativePtr);
}
@SuppressWarnings("unused")
public void setScaleY(float scaleY) {
nSetScaleY(mNativePtr, scaleY);
}
@SuppressWarnings("unused")
public float getTranslateX() {
return nGetTranslateX(mNativePtr);
}
@SuppressWarnings("unused")
public void setTranslateX(float translateX) {
nSetTranslateX(mNativePtr, translateX);
}
@SuppressWarnings("unused")
public float getTranslateY() {
return nGetTranslateY(mNativePtr);
}
@SuppressWarnings("unused")
public void setTranslateY(float translateY) {
nSetTranslateY(mNativePtr, translateY);
}
}
/**
* Common Path information for clip path and normal path.
*/
private static abstract class VPath implements VObject {
protected PathParser.PathData mPathData = null;
String mPathName;
int mChangingConfigurations;
public VPath() {
// Empty constructor.
}
public VPath(VPath copy) {
mPathName = copy.mPathName;
mChangingConfigurations = copy.mChangingConfigurations;
mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData);
}
public String getPathName() {
return mPathName;
}
/* Setters and Getters, used by animator from AnimatedVectorDrawable. */
@SuppressWarnings("unused")
public PathParser.PathData getPathData() {
return mPathData;
}
// TODO: Move the PathEvaluator and this setter and the getter above into native.
@SuppressWarnings("unused")
public void setPathData(PathParser.PathData pathData) {
mPathData.setPathData(pathData);
nSetPathData(getNativePtr(), mPathData.getNativePtr());
}
}
/**
* Clip path, which only has name and pathData.
*/
private static class VClipPath extends VPath {
long mNativePtr = 0;
public VClipPath() {
mNativePtr = nCreateClipPath();
// Empty constructor.
}
public VClipPath(VClipPath copy) {
super(copy);
mNativePtr = nCreateClipPath(copy.mNativePtr);
}
@Override
public long getNativePtr() {
return mNativePtr;
}
@Override
protected void finalize() throws Throwable {
if (mNativePtr != 0) {
nDestroy(mNativePtr);
mNativePtr = 0;
}
super.finalize();
}
@Override
public void inflate(Resources r, AttributeSet attrs, Theme theme) {
final TypedArray a = obtainAttributes(r, theme, attrs,
R.styleable.VectorDrawableClipPath);
updateStateFromTypedArray(a);
a.recycle();
}
@Override
public boolean canApplyTheme() {
return false;
}
@Override
public void applyTheme(Theme theme) {
// No-op.
}
@Override
public boolean onStateChange(int[] stateSet) {
return false;
}
@Override
public boolean isStateful() {
return false;
}
private void updateStateFromTypedArray(TypedArray a) {
// Account for any configuration changes.
mChangingConfigurations |= a.getChangingConfigurations();
final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name);
if (pathName != null) {
mPathName = pathName;
nSetName(mNativePtr, mPathName);
}
final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData);
if (pathDataString != null) {
mPathData = new PathParser.PathData(pathDataString);
nSetPathString(mNativePtr, pathDataString, pathDataString.length());
}
}
}
/**
* Normal path, which contains all the fill / paint information.
*/
private static class VFullPath extends VPath {
private static final int STROKE_WIDTH_INDEX = 0;
private static final int STROKE_COLOR_INDEX = 1;
private static final int STROKE_ALPHA_INDEX = 2;
private static final int FILL_COLOR_INDEX = 3;
private static final int FILL_ALPHA_INDEX = 4;
private static final int TRIM_PATH_START_INDEX = 5;
private static final int TRIM_PATH_END_INDEX = 6;
private static final int TRIM_PATH_OFFSET_INDEX = 7;
private static final int STROKE_LINE_CAP_INDEX = 8;
private static final int STROKE_LINE_JOIN_INDEX = 9;
private static final int STROKE_MITER_LIMIT_INDEX = 10;
private static final int TOTAL_PROPERTY_COUNT = 11;
// Temp array to store property data obtained from native getter.
private byte[] mPropertyData;
/////////////////////////////////////////////////////
// Variables below need to be copied (deep copy if applicable) for mutation.
private int[] mThemeAttrs;
ColorStateList mStrokeColors = null;
ColorStateList mFillColors = null;
private long mNativePtr = 0;
public VFullPath() {
// Empty constructor.
mNativePtr = nCreateFullPath();
}
public VFullPath(VFullPath copy) {
super(copy);
mNativePtr = nCreateFullPath(copy.mNativePtr);
mThemeAttrs = copy.mThemeAttrs;
mStrokeColors = copy.mStrokeColors;
mFillColors = copy.mFillColors;
}
@Override
public boolean onStateChange(int[] stateSet) {
boolean changed = false;
if (mStrokeColors != null) {
final int oldStrokeColor = getStrokeColor();
final int newStrokeColor = mStrokeColors.getColorForState(stateSet, oldStrokeColor);
changed |= oldStrokeColor != newStrokeColor;
if (oldStrokeColor != newStrokeColor) {
nSetStrokeColor(mNativePtr, newStrokeColor);
}
}
if (mFillColors != null) {
final int oldFillColor = getFillColor();
final int newFillColor = mFillColors.getColorForState(stateSet, oldFillColor);
changed |= oldFillColor != newFillColor;
if (oldFillColor != newFillColor) {
nSetFillColor(mNativePtr, newFillColor);
}
}
return changed;
}
@Override
public boolean isStateful() {
return mStrokeColors != null || mFillColors != null;
}
@Override
public long getNativePtr() {
return mNativePtr;
}
@Override
public void inflate(Resources r, AttributeSet attrs, Theme theme) {
final TypedArray a = obtainAttributes(r, theme, attrs,
R.styleable.VectorDrawablePath);
updateStateFromTypedArray(a);
a.recycle();
}
@Override
protected void finalize() throws Throwable {
if (mNativePtr != 0) {
nDestroy(mNativePtr);
mNativePtr = 0;
}
super.finalize();
}
private void updateStateFromTypedArray(TypedArray a) {
int byteCount = TOTAL_PROPERTY_COUNT * 4;
if (mPropertyData == null) {
// Lazy initialization: If the path is created through copy constructor, this may
// never get called.
mPropertyData = new byte[byteCount];
}
// The bulk getters/setters of property data (e.g. stroke width, color, etc) allows us
// to pull current values from native and store modifications with only two methods,
// minimizing JNI overhead.
boolean success = nGetFullPathProperties(mNativePtr, mPropertyData, byteCount);
if (!success) {
throw new RuntimeException("Error: inconsistent property count");
}
ByteBuffer properties = ByteBuffer.wrap(mPropertyData);
properties.order(ByteOrder.nativeOrder());
float strokeWidth = properties.getFloat(STROKE_WIDTH_INDEX * 4);
int strokeColor = properties.getInt(STROKE_COLOR_INDEX * 4);
float strokeAlpha = properties.getFloat(STROKE_ALPHA_INDEX * 4);
int fillColor = properties.getInt(FILL_COLOR_INDEX * 4);
float fillAlpha = properties.getFloat(FILL_ALPHA_INDEX * 4);
float trimPathStart = properties.getFloat(TRIM_PATH_START_INDEX * 4);
float trimPathEnd = properties.getFloat(TRIM_PATH_END_INDEX * 4);
float trimPathOffset = properties.getFloat(TRIM_PATH_OFFSET_INDEX * 4);
int strokeLineCap = properties.getInt(STROKE_LINE_CAP_INDEX * 4);
int strokeLineJoin = properties.getInt(STROKE_LINE_JOIN_INDEX * 4);
float strokeMiterLimit = properties.getFloat(STROKE_MITER_LIMIT_INDEX * 4);
// Account for any configuration changes.
mChangingConfigurations |= a.getChangingConfigurations();
// Extract the theme attributes, if any.
mThemeAttrs = a.extractThemeAttrs();
final String pathName = a.getString(R.styleable.VectorDrawablePath_name);
if (pathName != null) {
mPathName = pathName;
nSetName(mNativePtr, mPathName);
}
final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData);
if (pathString != null) {
mPathData = new PathParser.PathData(pathString);
nSetPathString(mNativePtr, pathString, pathString.length());
}
final ColorStateList fillColors = a.getColorStateList(
R.styleable.VectorDrawablePath_fillColor);
if (fillColors != null) {
// If the color state list isn't stateful, discard the state
// list and keep the default (e.g. the only) color.
mFillColors = fillColors.isStateful() ? fillColors : null;
fillColor = fillColors.getDefaultColor();
}
final ColorStateList strokeColors = a.getColorStateList(
R.styleable.VectorDrawablePath_strokeColor);
if (strokeColors != null) {
// If the color state list isn't stateful, discard the state
// list and keep the default (e.g. the only) color.
mStrokeColors = strokeColors.isStateful() ? strokeColors : null;
strokeColor = strokeColors.getDefaultColor();
}
fillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, fillAlpha);
strokeLineCap = a.getInt(
R.styleable.VectorDrawablePath_strokeLineCap, strokeLineCap);
strokeLineJoin = a.getInt(
R.styleable.VectorDrawablePath_strokeLineJoin, strokeLineJoin);
strokeMiterLimit = a.getFloat(
R.styleable.VectorDrawablePath_strokeMiterLimit, strokeMiterLimit);
strokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha,
strokeAlpha);
strokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
strokeWidth);
trimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
trimPathEnd);
trimPathOffset = a.getFloat(
R.styleable.VectorDrawablePath_trimPathOffset, trimPathOffset);
trimPathStart = a.getFloat(
R.styleable.VectorDrawablePath_trimPathStart, trimPathStart);
nUpdateFullPathProperties(mNativePtr, strokeWidth, strokeColor, strokeAlpha,
fillColor, fillAlpha, trimPathStart, trimPathEnd, trimPathOffset,
strokeMiterLimit, strokeLineCap, strokeLineJoin);
}
@Override
public boolean canApplyTheme() {
return mThemeAttrs != null;
}
@Override
public void applyTheme(Theme t) {
if (mThemeAttrs == null) {
return;
}
final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath);
updateStateFromTypedArray(a);
a.recycle();
}
/* Setters and Getters, used by animator from AnimatedVectorDrawable. */
@SuppressWarnings("unused")
int getStrokeColor() {
return nGetStrokeColor(mNativePtr);
}
@SuppressWarnings("unused")
void setStrokeColor(int strokeColor) {
mStrokeColors = null;
nSetStrokeColor(mNativePtr, strokeColor);
}
@SuppressWarnings("unused")
float getStrokeWidth() {
return nGetStrokeWidth(mNativePtr);
}
@SuppressWarnings("unused")
void setStrokeWidth(float strokeWidth) {
nSetStrokeWidth(mNativePtr, strokeWidth);
}
@SuppressWarnings("unused")
float getStrokeAlpha() {
return nGetStrokeAlpha(mNativePtr);
}
@SuppressWarnings("unused")
void setStrokeAlpha(float strokeAlpha) {
nSetStrokeAlpha(mNativePtr, strokeAlpha);
}
@SuppressWarnings("unused")
int getFillColor() {
return nGetFillColor(mNativePtr);
}
@SuppressWarnings("unused")
void setFillColor(int fillColor) {
mFillColors = null;
nSetFillColor(mNativePtr, fillColor);
}
@SuppressWarnings("unused")
float getFillAlpha() {
return nGetFillAlpha(mNativePtr);
}
@SuppressWarnings("unused")
void setFillAlpha(float fillAlpha) {
nSetFillAlpha(mNativePtr, fillAlpha);
}
@SuppressWarnings("unused")
float getTrimPathStart() {
return nGetTrimPathStart(mNativePtr);
}
@SuppressWarnings("unused")
void setTrimPathStart(float trimPathStart) {
nSetTrimPathStart(mNativePtr, trimPathStart);
}
@SuppressWarnings("unused")
float getTrimPathEnd() {
return nGetTrimPathEnd(mNativePtr);
}
@SuppressWarnings("unused")
void setTrimPathEnd(float trimPathEnd) {
nSetTrimPathEnd(mNativePtr, trimPathEnd);
}
@SuppressWarnings("unused")
float getTrimPathOffset() {
return nGetTrimPathOffset(mNativePtr);
}
@SuppressWarnings("unused")
void setTrimPathOffset(float trimPathOffset) {
nSetTrimPathOffset(mNativePtr, trimPathOffset);
}
}
interface VObject {
long getNativePtr();
void inflate(Resources r, AttributeSet attrs, Theme theme);
boolean canApplyTheme();
void applyTheme(Theme t);
boolean onStateChange(int[] state);
boolean isStateful();
}
private static native long nCreateRenderer(long rootGroupPtr);
private static native void nDestroyRenderer(long rendererPtr);
private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
float viewportHeight);
private static native boolean nSetRootAlpha(long rendererPtr, float alpha);
private static native float nGetRootAlpha(long rendererPtr);
private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching);
private static native void nDraw(long rendererPtr, long canvasWrapperPtr,
long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache);
private static native long nCreateFullPath();
private static native long nCreateFullPath(long mNativeFullPathPtr);
private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties,
int length);
private static native void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
int strokeLineJoin);
private static native long nCreateClipPath();
private static native long nCreateClipPath(long clipPathPtr);
private static native long nCreateGroup();
private static native long nCreateGroup(long groupPtr);
private static native void nDestroy(long nodePtr);
private static native void nSetName(long nodePtr, String name);
private static native boolean nGetGroupProperties(long groupPtr, float[] properties,
int length);
private static native void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
float pivotY, float scaleX, float scaleY, float translateX, float translateY);
private static native void nAddChild(long groupPtr, long nodePtr);
private static native void nSetPathString(long pathPtr, String pathString, int length);
/**
* The setters and getters below for paths and groups are here temporarily, and will be
* removed once the animation in AVD is replaced with RenderNodeAnimator, in which case the
* animation will modify these properties in native. By then no JNI hopping would be necessary
* for VD during animation, and these setters and getters will be obsolete.
*/
// Setters and getters during animation.
private static native float nGetRotation(long groupPtr);
private static native void nSetRotation(long groupPtr, float rotation);
private static native float nGetPivotX(long groupPtr);
private static native void nSetPivotX(long groupPtr, float pivotX);
private static native float nGetPivotY(long groupPtr);
private static native void nSetPivotY(long groupPtr, float pivotY);
private static native float nGetScaleX(long groupPtr);
private static native void nSetScaleX(long groupPtr, float scaleX);
private static native float nGetScaleY(long groupPtr);
private static native void nSetScaleY(long groupPtr, float scaleY);
private static native float nGetTranslateX(long groupPtr);
private static native void nSetTranslateX(long groupPtr, float translateX);
private static native float nGetTranslateY(long groupPtr);
private static native void nSetTranslateY(long groupPtr, float translateY);
// Setters and getters for VPath during animation.
private static native void nSetPathData(long pathPtr, long pathDataPtr);
private static native float nGetStrokeWidth(long pathPtr);
private static native void nSetStrokeWidth(long pathPtr, float width);
private static native int nGetStrokeColor(long pathPtr);
private static native void nSetStrokeColor(long pathPtr, int strokeColor);
private static native float nGetStrokeAlpha(long pathPtr);
private static native void nSetStrokeAlpha(long pathPtr, float alpha);
private static native int nGetFillColor(long pathPtr);
private static native void nSetFillColor(long pathPtr, int fillColor);
private static native float nGetFillAlpha(long pathPtr);
private static native void nSetFillAlpha(long pathPtr, float fillAlpha);
private static native float nGetTrimPathStart(long pathPtr);
private static native void nSetTrimPathStart(long pathPtr, float trimPathStart);
private static native float nGetTrimPathEnd(long pathPtr);
private static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd);
private static native float nGetTrimPathOffset(long pathPtr);
private static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset);
}