diff options
Diffstat (limited to 'graphics/java/android')
| -rw-r--r-- | graphics/java/android/graphics/Bitmap.java | 7 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Canvas.java | 10 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Paint.java | 6 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Path.java | 7 | ||||
| -rw-r--r-- | graphics/java/android/graphics/PathDashPathEffect.java | 2 | ||||
| -rw-r--r-- | graphics/java/android/graphics/PathMeasure.java | 7 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Picture.java | 13 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Region.java | 6 | ||||
| -rw-r--r-- | graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java | 280 | ||||
| -rw-r--r-- | graphics/java/android/graphics/drawable/GradientDrawable.java | 2 | ||||
| -rw-r--r-- | graphics/java/android/graphics/drawable/VectorDrawable.java | 333 |
11 files changed, 561 insertions, 112 deletions
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 1fdc1f575bd4..49721cf42666 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -1667,10 +1667,10 @@ public final class Bitmap implements Parcelable { * and therefore is harmless. */ public void prepareToDraw() { - // TODO: Consider having this start an async upload? - // With inPurgeable no-op'd there's currently no use for this - // method, but it could have interesting future uses. checkRecycled("Can't prepareToDraw on a recycled bitmap!"); + // Kick off an update/upload of the bitmap outside of the normal + // draw path. + nativePrepareToDraw(mNativePtr); } /** @@ -1741,4 +1741,5 @@ public final class Bitmap implements Parcelable { private static native void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap); private static native boolean nativeSameAs(long nativeBitmap0, long nativeBitmap1); private static native long nativeRefPixelRef(long nativeBitmap); + private static native void nativePrepareToDraw(long nativeBitmap); } diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index cb6c92e87172..40b877de131c 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -788,7 +788,7 @@ public class Canvas { * @return true if the resulting is non-empty */ public boolean clipPath(@NonNull Path path, @NonNull Region.Op op) { - return native_clipPath(mNativeCanvasWrapper, path.ni(), op.nativeInt); + return native_clipPath(mNativeCanvasWrapper, path.readOnlyNI(), op.nativeInt); } /** @@ -907,7 +907,7 @@ public class Canvas { * does not intersect with the canvas' clip */ public boolean quickReject(@NonNull Path path, @NonNull EdgeType type) { - return native_quickReject(mNativeCanvasWrapper, path.ni()); + return native_quickReject(mNativeCanvasWrapper, path.readOnlyNI()); } /** @@ -1259,7 +1259,7 @@ public class Canvas { if (path.isSimplePath && path.rects != null) { native_drawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance()); } else { - native_drawPath(mNativeCanvasWrapper, path.ni(), paint.getNativeInstance()); + native_drawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance()); } } @@ -1895,7 +1895,7 @@ public class Canvas { throw new ArrayIndexOutOfBoundsException(); } native_drawTextOnPath(mNativeCanvasWrapper, text, index, count, - path.ni(), hOffset, vOffset, + path.readOnlyNI(), hOffset, vOffset, paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface); } @@ -1915,7 +1915,7 @@ public class Canvas { public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset, float vOffset, @NonNull Paint paint) { if (text.length() > 0) { - native_drawTextOnPath(mNativeCanvasWrapper, text, path.ni(), hOffset, vOffset, + native_drawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset, paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface); } } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 054e29fd9ff4..cfbe672a4cd6 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -1021,7 +1021,7 @@ public class Paint { * drawn with a hairline (width == 0) */ public boolean getFillPath(Path src, Path dst) { - return nGetFillPath(mNativePaint, src.ni(), dst.ni()); + return nGetFillPath(mNativePaint, src.readOnlyNI(), dst.mutateNI()); } /** @@ -2394,7 +2394,7 @@ public class Paint { throw new ArrayIndexOutOfBoundsException(); } nGetTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, index, count, x, y, - path.ni()); + path.mutateNI()); } /** @@ -2416,7 +2416,7 @@ public class Paint { throw new IndexOutOfBoundsException(); } nGetTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, start, end, x, y, - path.ni()); + path.mutateNI()); } /** diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index de391af5d4ce..be31bbe0c086 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -775,7 +775,12 @@ public class Path { } } - final long ni() { + final long readOnlyNI() { + return mNativePath; + } + + final long mutateNI() { + isSimplePath = false; return mNativePath; } diff --git a/graphics/java/android/graphics/PathDashPathEffect.java b/graphics/java/android/graphics/PathDashPathEffect.java index 4f43f680ff3e..2b6a6edcc266 100644 --- a/graphics/java/android/graphics/PathDashPathEffect.java +++ b/graphics/java/android/graphics/PathDashPathEffect.java @@ -41,7 +41,7 @@ public class PathDashPathEffect extends PathEffect { */ public PathDashPathEffect(Path shape, float advance, float phase, Style style) { - native_instance = nativeCreate(shape.ni(), advance, phase, + native_instance = nativeCreate(shape.readOnlyNI(), advance, phase, style.native_style); } diff --git a/graphics/java/android/graphics/PathMeasure.java b/graphics/java/android/graphics/PathMeasure.java index 2848949eb72f..78d892e94cf0 100644 --- a/graphics/java/android/graphics/PathMeasure.java +++ b/graphics/java/android/graphics/PathMeasure.java @@ -50,7 +50,7 @@ public class PathMeasure { public PathMeasure(Path path, boolean forceClosed) { // The native implementation does not copy the path, prevent it from being GC'd mPath = path; - native_instance = native_create(path != null ? path.ni() : 0, + native_instance = native_create(path != null ? path.readOnlyNI() : 0, forceClosed); } @@ -60,7 +60,7 @@ public class PathMeasure { public void setPath(Path path, boolean forceClosed) { mPath = path; native_setPath(native_instance, - path != null ? path.ni() : 0, + path != null ? path.readOnlyNI() : 0, forceClosed); } @@ -134,8 +134,7 @@ public class PathMeasure { return false; } - dst.isSimplePath = false; - return native_getSegment(native_instance, startD, stopD, dst.ni(), startWithMoveTo); + return native_getSegment(native_instance, startD, stopD, dst.mutateNI(), startWithMoveTo); } /** diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index 28d869063225..08eeaff69f9b 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -21,11 +21,14 @@ import java.io.OutputStream; /** * A Picture records drawing calls (via the canvas returned by beginRecording) - * and can then play them back into Canvas (via {@link Picture#draw(Canvas)} or + * and can then play them back into Canvas (via {@link Picture#draw(Canvas)} or * {@link Canvas#drawPicture(Picture)}).For most content (e.g. text, lines, rectangles), * drawing a sequence from a picture can be faster than the equivalent API * calls, since the picture performs its playback without incurring any * method-call overhead. + * + * <p class="note"><strong>Note:</strong> Prior to API level 23 a picture cannot + * be replayed on a hardware accelerated canvas.</p> */ public class Picture { private Canvas mRecordingCanvas; @@ -135,10 +138,6 @@ public class Picture { * have been persisted across device restarts are not guaranteed to decode * properly and are highly discouraged. * - * <p> - * <strong>Note:</strong> Prior to API level 23 a picture created from an - * input stream cannot be replayed on a hardware accelerated canvas. - * * @see #writeToStream(java.io.OutputStream) * @deprecated The recommended alternative is to not use writeToStream and * instead draw the picture into a Bitmap from which you can persist it as @@ -155,10 +154,6 @@ public class Picture { * The resulting stream is NOT to be persisted across device restarts as * there is no guarantee that the Picture can be successfully reconstructed. * - * <p> - * <strong>Note:</strong> Prior to API level 23 a picture created from an - * input stream cannot be replayed on a hardware accelerated canvas. - * * @see #createFromStream(java.io.InputStream) * @deprecated The recommended alternative is to draw the picture into a * Bitmap from which you can persist it as raw or compressed pixels. diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java index de89ad07d873..dca6d9ed3b2f 100644 --- a/graphics/java/android/graphics/Region.java +++ b/graphics/java/android/graphics/Region.java @@ -110,7 +110,7 @@ public class Region implements Parcelable { * (with no antialiasing). */ public boolean setPath(Path path, Region clip) { - return nativeSetPath(mNativeRegion, path.ni(), clip.mNativeRegion); + return nativeSetPath(mNativeRegion, path.readOnlyNI(), clip.mNativeRegion); } /** @@ -155,7 +155,7 @@ public class Region implements Parcelable { */ public Path getBoundaryPath() { Path path = new Path(); - nativeGetBoundaryPath(mNativeRegion, path.ni()); + nativeGetBoundaryPath(mNativeRegion, path.mutateNI()); return path; } @@ -164,7 +164,7 @@ public class Region implements Parcelable { * path will also be empty. */ public boolean getBoundaryPath(Path path) { - return nativeGetBoundaryPath(mNativeRegion, path.ni()); + return nativeGetBoundaryPath(mNativeRegion, path.mutateNI()); } /** diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index ee6780722f0c..c836204486b0 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -46,6 +46,7 @@ import android.util.IntArray; import android.util.Log; import android.util.LongArray; import android.util.PathParser; +import android.util.Property; import android.util.TimeUtils; import android.view.Choreographer; import android.view.DisplayListCanvas; @@ -157,7 +158,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; /** Local, mutable animator set. */ - private VectorDrawableAnimator mAnimatorSet = new VectorDrawableAnimatorUI(this); + private VectorDrawableAnimator mAnimatorSet; /** * The resources against which this drawable was created. Used to attempt @@ -182,6 +183,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) { mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res); + mAnimatorSet = new VectorDrawableAnimatorRT(this); mRes = res; } @@ -237,6 +239,14 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { @Override public void draw(Canvas canvas) { + if (!canvas.isHardwareAccelerated() && mAnimatorSet instanceof VectorDrawableAnimatorRT) { + // If we have SW canvas and the RT animation is waiting to start, We need to fallback + // to UI thread animation for AVD. + if (!mAnimatorSet.isRunning() && + ((VectorDrawableAnimatorRT) mAnimatorSet).mPendingAnimationActions.size() > 0) { + fallbackOntoUI(); + } + } mAnimatorSet.onDraw(canvas); mAnimatedVectorState.mVectorDrawable.draw(canvas); } @@ -390,9 +400,12 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { R.styleable.AnimatedVectorDrawableTarget_animation, 0); if (animResId != 0) { if (theme != null) { - final Animator objectAnimator = AnimatorInflater.loadAnimator( + // The animator here could be ObjectAnimator or AnimatorSet. + final Animator animator = AnimatorInflater.loadAnimator( res, theme, animResId, pathErrorScale); - state.addTargetAnimator(target, objectAnimator); + updateAnimatorProperty(animator, target, state.mVectorDrawable, + state.mShouldIgnoreInvalidAnim); + state.addTargetAnimator(target, animator); } else { // The animation may be theme-dependent. As a // workaround until Animator has full support for @@ -414,6 +427,55 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { mRes = state.mPendingAnims == null ? null : res; } + private static void updateAnimatorProperty(Animator animator, String targetName, + VectorDrawable vectorDrawable, boolean ignoreInvalidAnim) { + if (animator instanceof ObjectAnimator) { + // Change the property of the Animator from using reflection based on the property + // name to a Property object that wraps the setter and getter for modifying that + // specific property for a given object. By replacing the reflection with a direct call, + // we can largely reduce the time it takes for a animator to modify a VD property. + PropertyValuesHolder[] holders = ((ObjectAnimator) animator).getValues(); + for (int i = 0; i < holders.length; i++) { + PropertyValuesHolder pvh = holders[i]; + String propertyName = pvh.getPropertyName(); + Object targetNameObj = vectorDrawable.getTargetByName(targetName); + Property property = null; + if (targetNameObj instanceof VectorDrawable.VObject) { + property = ((VectorDrawable.VObject) targetNameObj).getProperty(propertyName); + } else if (targetNameObj instanceof VectorDrawable.VectorDrawableState) { + property = ((VectorDrawable.VectorDrawableState) targetNameObj) + .getProperty(propertyName); + } + if (property != null) { + if (containsSameValueType(pvh, property)) { + pvh.setProperty(property); + } else if (!ignoreInvalidAnim) { + throw new RuntimeException("Wrong valueType for Property: " + propertyName + + ". Expected type: " + property.getType().toString() + ". Actual " + + "type defined in resources: " + pvh.getValueType().toString()); + + } + } + } + } else if (animator instanceof AnimatorSet) { + for (Animator anim : ((AnimatorSet) animator).getChildAnimations()) { + updateAnimatorProperty(anim, targetName, vectorDrawable, ignoreInvalidAnim); + } + } + } + + private static boolean containsSameValueType(PropertyValuesHolder holder, Property property) { + Class type1 = holder.getValueType(); + Class type2 = property.getType(); + if (type1 == float.class || type1 == Float.class) { + return type2 == float.class || type2 == Float.class; + } else if (type1 == int.class || type1 == Integer.class) { + return type2 == int.class || type2 == Integer.class; + } else { + return type1 == type2; + } + } + /** * Force to animate on UI thread. * @hide @@ -425,10 +487,22 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { throw new UnsupportedOperationException("Cannot force Animated Vector Drawable to" + " run on UI thread when the animation has started on RenderThread."); } + fallbackOntoUI(); + } + } + + private void fallbackOntoUI() { + if (mAnimatorSet instanceof VectorDrawableAnimatorRT) { + VectorDrawableAnimatorRT oldAnim = (VectorDrawableAnimatorRT) mAnimatorSet; mAnimatorSet = new VectorDrawableAnimatorUI(this); if (mAnimatorSetFromXml != null) { mAnimatorSet.init(mAnimatorSetFromXml); } + // Transfer the listener from RT animator to UI animator + if (oldAnim.mListener != null) { + mAnimatorSet.setListener(oldAnim.mListener); + } + oldAnim.transferPendingActions(mAnimatorSet); } } @@ -462,6 +536,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { @Config int mChangingConfigurations; VectorDrawable mVectorDrawable; + private final boolean mShouldIgnoreInvalidAnim; + /** Animators that require a theme before inflation. */ ArrayList<PendingAnimator> mPendingAnims; @@ -473,6 +549,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy, Callback owner, Resources res) { + mShouldIgnoreInvalidAnim = shouldIgnoreInvalidAnimation(); if (copy != null) { mChangingConfigurations = copy.mChangingConfigurations; @@ -616,8 +693,10 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { for (int i = 0, count = pendingAnims.size(); i < count; i++) { final PendingAnimator pendingAnimator = pendingAnims.get(i); - final Animator objectAnimator = pendingAnimator.newInstance(res, t); - addTargetAnimator(pendingAnimator.target, objectAnimator); + final Animator animator = pendingAnimator.newInstance(res, t); + updateAnimatorProperty(animator, pendingAnimator.target, mVectorDrawable, + mShouldIgnoreInvalidAnim); + addTargetAnimator(pendingAnimator.target, animator); } } } @@ -982,6 +1061,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private static final int REVERSE_ANIMATION = 2; private static final int RESET_ANIMATION = 3; private static final int END_ANIMATION = 4; + + // If the duration of an animation is more than 300 frames, we cap the sample size to 300. + private static final int MAX_SAMPLE_POINTS = 300; private AnimatorListener mListener = null; private final LongArray mStartDelays = new LongArray(); private PropertyValuesHolder.PropertyValues mTmpValues = @@ -992,14 +1074,12 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private boolean mInitialized = false; private boolean mIsReversible = false; private boolean mIsInfinite = false; - // This needs to be set before parsing starts. - private boolean mShouldIgnoreInvalidAnim; // TODO: Consider using NativeAllocationRegistery to track native allocation private final VirtualRefBasePtr mSetRefBasePtr; private WeakReference<RenderNode> mLastSeenTarget = null; private int mLastListenerId = 0; private final IntArray mPendingAnimationActions = new IntArray(); - private final Drawable mDrawable; + private final AnimatedVectorDrawable mDrawable; VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) { mDrawable = drawable; @@ -1016,8 +1096,10 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " + "re-initialized"); } - mShouldIgnoreInvalidAnim = shouldIgnoreInvalidAnimation(); parseAnimatorSet(set, 0); + long vectorDrawableTreePtr = mDrawable.mAnimatedVectorState.mVectorDrawable + .getNativeTree(); + nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr); mInitialized = true; mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE; @@ -1077,7 +1159,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } else if (target instanceof VectorDrawable.VFullPath) { createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target, startTime); - } else if (!mShouldIgnoreInvalidAnim) { + } else if (!mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { throw new IllegalArgumentException("ClipPath only supports PathData " + "property"); } @@ -1086,7 +1168,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } else if (target instanceof VectorDrawable.VectorDrawableState) { createRTAnimatorForRootGroup(values, animator, (VectorDrawable.VectorDrawableState) target, startTime); - } else if (!mShouldIgnoreInvalidAnim) { + } else if (!mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { // Should never get here throw new UnsupportedOperationException("Target should be either VGroup, VPath, " + "or ConstantState, " + target == null ? "Null target" : target.getClass() + @@ -1121,8 +1203,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId, (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); if (mTmpValues.dataSource != null) { - float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator - .getDuration()); + float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, + animator.getDuration()); nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); } createNativeChildAnimator(propertyPtr, startTime, animator); @@ -1149,7 +1231,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { long nativePtr = target.getNativePtr(); if (mTmpValues.type == Float.class || mTmpValues.type == float.class) { if (propertyId < 0) { - if (mShouldIgnoreInvalidAnim) { + if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { return; } else { throw new IllegalArgumentException("Property: " + mTmpValues.propertyName @@ -1158,12 +1240,24 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId, (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); + if (mTmpValues.dataSource != null) { + // Pass keyframe data to native, if any. + float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, + animator.getDuration()); + nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); + } } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) { propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId, (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue); + if (mTmpValues.dataSource != null) { + // Pass keyframe data to native, if any. + int[] dataPoints = createIntDataPoints(mTmpValues.dataSource, + animator.getDuration()); + nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); + } } else { - if (mShouldIgnoreInvalidAnim) { + if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { return; } else { throw new UnsupportedOperationException("Unsupported type: " + @@ -1171,45 +1265,63 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { "supported for Paths."); } } - if (mTmpValues.dataSource != null) { - float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator - .getDuration()); - nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); - } createNativeChildAnimator(propertyPtr, startTime, animator); } private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values, ObjectAnimator animator, VectorDrawable.VectorDrawableState target, long startTime) { - long nativePtr = target.getNativeRenderer(); - if (!animator.getPropertyName().equals("alpha")) { - if (mShouldIgnoreInvalidAnim) { - return; - } else { - throw new UnsupportedOperationException("Only alpha is supported for root " - + "group"); - } + long nativePtr = target.getNativeRenderer(); + if (!animator.getPropertyName().equals("alpha")) { + if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { + return; + } else { + throw new UnsupportedOperationException("Only alpha is supported for root " + + "group"); } - Float startValue = null; - Float endValue = null; - for (int i = 0; i < values.length; i++) { - values[i].getPropertyValues(mTmpValues); - if (mTmpValues.propertyName.equals("alpha")) { - startValue = (Float) mTmpValues.startValue; - endValue = (Float) mTmpValues.endValue; - break; - } + } + Float startValue = null; + Float endValue = null; + for (int i = 0; i < values.length; i++) { + values[i].getPropertyValues(mTmpValues); + if (mTmpValues.propertyName.equals("alpha")) { + startValue = (Float) mTmpValues.startValue; + endValue = (Float) mTmpValues.endValue; + break; } - if (startValue == null && endValue == null) { - if (mShouldIgnoreInvalidAnim) { - return; - } else { - throw new UnsupportedOperationException("No alpha values are specified"); - } + } + if (startValue == null && endValue == null) { + if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { + return; + } else { + throw new UnsupportedOperationException("No alpha values are specified"); } - long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue); - createNativeChildAnimator(propertyPtr, startTime, animator); + } + long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue); + if (mTmpValues.dataSource != null) { + // Pass keyframe data to native, if any. + float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, + animator.getDuration()); + nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); + } + createNativeChildAnimator(propertyPtr, startTime, animator); + } + + /** + * Calculate the amount of frames an animation will run based on duration. + */ + private static int getFrameCount(long duration) { + long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos(); + int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS); + int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs); + // We need 2 frames of data minimum. + numAnimFrames = Math.max(2, numAnimFrames); + if (numAnimFrames > MAX_SAMPLE_POINTS) { + Log.w("AnimatedVectorDrawable", "Duration for the animation is too long :" + + duration + ", the animation will subsample the keyframe or path data."); + numAnimFrames = MAX_SAMPLE_POINTS; + } + return numAnimFrames; } // These are the data points that define the value of the animating properties. @@ -1217,11 +1329,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { // a point on the path corresponds to the values of translateX and translateY. // TODO: (Optimization) We should pass the path down in native and chop it into segments // in native. - private static float[] createDataPoints( + private static float[] createFloatDataPoints( PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { - long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos(); - int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS); - int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs); + int numAnimFrames = getFrameCount(duration); float values[] = new float[numAnimFrames]; float lastFrame = numAnimFrames - 1; for (int i = 0; i < numAnimFrames; i++) { @@ -1231,6 +1341,18 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { return values; } + private static int[] createIntDataPoints( + PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { + int numAnimFrames = getFrameCount(duration); + int values[] = new int[numAnimFrames]; + float lastFrame = numAnimFrames - 1; + for (int i = 0; i < numAnimFrames; i++) { + float fraction = i / lastFrame; + values[i] = (Integer) dataSource.getValueAtFraction(fraction); + } + return values; + } + private void createNativeChildAnimator(long propertyPtr, long extraDelay, ObjectAnimator animator) { long duration = animator.getDuration(); @@ -1245,7 +1367,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { mStartDelays.add(startDelay); nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration, - repeatCount); + repeatCount, animator.getRepeatMode()); } /** @@ -1254,16 +1376,19 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { * to the last seen RenderNode target and start right away. */ protected void recordLastSeenTarget(DisplayListCanvas canvas) { - mLastSeenTarget = new WeakReference<RenderNode>( - RenderNodeAnimatorSetHelper.getTarget(canvas)); - if (mPendingAnimationActions.size() > 0 && useLastSeenTarget()) { - if (DBG_ANIMATION_VECTOR_DRAWABLE) { - Log.d(LOGTAG, "Target is set in the next frame"); - } - for (int i = 0; i < mPendingAnimationActions.size(); i++) { - handlePendingAction(mPendingAnimationActions.get(i)); + final RenderNode node = RenderNodeAnimatorSetHelper.getTarget(canvas); + mLastSeenTarget = new WeakReference<RenderNode>(node); + // Add the animator to the list of animators on every draw + if (mInitialized || mPendingAnimationActions.size() > 0) { + if (useTarget(node)) { + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.d(LOGTAG, "Target is set in the next frame"); + } + for (int i = 0; i < mPendingAnimationActions.size(); i++) { + handlePendingAction(mPendingAnimationActions.get(i)); + } + mPendingAnimationActions.clear(); } - mPendingAnimationActions.clear(); } } @@ -1285,10 +1410,15 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private boolean useLastSeenTarget() { if (mLastSeenTarget != null) { final RenderNode target = mLastSeenTarget.get(); - if (target != null && target.isAttached()) { - target.addAnimator(this); - return true; - } + return useTarget(target); + } + return false; + } + + private boolean useTarget(RenderNode target) { + if (target != null && target.isAttached()) { + target.registerVectorDrawableAnimator(this); + return true; } return false; } @@ -1480,11 +1610,32 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private static void callOnFinished(VectorDrawableAnimatorRT set, int id) { set.onAnimationEnd(id); } + + private void transferPendingActions(VectorDrawableAnimator animatorSet) { + for (int i = 0; i < mPendingAnimationActions.size(); i++) { + int pendingAction = mPendingAnimationActions.get(i); + if (pendingAction == START_ANIMATION) { + animatorSet.start(); + } else if (pendingAction == END_ANIMATION) { + animatorSet.end(); + } else if (pendingAction == REVERSE_ANIMATION) { + animatorSet.reverse(); + } else if (pendingAction == RESET_ANIMATION) { + animatorSet.reset(); + } else { + throw new UnsupportedOperationException("Animation action " + + pendingAction + "is not supported"); + } + } + mPendingAnimationActions.clear(); + } } private static native long nCreateAnimatorSet(); + private static native void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr); private static native void nAddAnimator(long setPtr, long propertyValuesHolder, - long nativeInterpolator, long startDelay, long duration, int repeatCount); + long nativeInterpolator, long startDelay, long duration, int repeatCount, + int repeatMode); private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId, float startValue, float endValue); @@ -1498,6 +1649,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, float endValue); private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length); + private static native void nSetPropertyHolderData(long nativePtr, int[] data, int length); private static native void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id); private static native void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id); private static native void nEnd(long animatorSetPtr); diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index bcc354c5b736..3dbd2a96b00a 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -699,7 +699,7 @@ public class GradientDrawable extends Drawable { float rad = mStrokePaint.getStrokeWidth(); canvas.saveLayer(mRect.left - rad, mRect.top - rad, mRect.right + rad, mRect.bottom + rad, - mLayerPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG); + mLayerPaint); // don't perform the filter in our individual paints // since the layer will do it for us diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index c855c4c6d17b..dc1d18f3b2bd 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -34,9 +34,12 @@ import android.graphics.Shader; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.FloatProperty; +import android.util.IntProperty; import android.util.LayoutDirection; import android.util.Log; import android.util.PathParser; +import android.util.Property; import android.util.Xml; import com.android.internal.R; @@ -232,7 +235,7 @@ public class VectorDrawable extends Drawable { private final Rect mTmpBounds = new Rect(); public VectorDrawable() { - this(new VectorDrawableState(), null); + this(new VectorDrawableState(null), null); } /** @@ -763,6 +766,13 @@ public class VectorDrawable extends Drawable { return mVectorState.mAutoMirrored; } + /** + * @hide + */ + public long getNativeTree() { + return mVectorState.getNativeRenderer(); + } + static class VectorDrawableState extends ConstantState { // Variables below need to be copied (deep copy if applicable) for mutation. int[] mThemeAttrs; @@ -795,12 +805,33 @@ public class VectorDrawable extends Drawable { int mLastSWCachePixelCount = 0; int mLastHWCachePixelCount = 0; + final static Property<VectorDrawableState, Float> ALPHA = + new FloatProperty<VectorDrawableState>("alpha") { + @Override + public void setValue(VectorDrawableState state, float value) { + state.setAlpha(value); + } + + @Override + public Float get(VectorDrawableState state) { + return state.getAlpha(); + } + }; + + Property getProperty(String propertyName) { + if (ALPHA.getName().equals(propertyName)) { + return ALPHA; + } + return null; + } + // This tracks the total native allocation for all the nodes. private int mAllocationOfAllNodes = 0; private static final int NATIVE_ALLOCATION_SIZE = 316; - // Deep copy for mutate() or implicitly mutate. + // If copy is not null, deep copy the given VectorDrawableState. Otherwise, create a + // native vector drawable tree with an empty root group. public VectorDrawableState(VectorDrawableState copy) { if (copy != null) { mThemeAttrs = copy.mThemeAttrs; @@ -821,8 +852,11 @@ public class VectorDrawable extends Drawable { if (copy.mRootName != null) { mVGTargetsMap.put(copy.mRootName, this); } - onTreeConstructionFinished(); + } else { + mRootGroup = new VGroup(); + createNativeTree(mRootGroup); } + onTreeConstructionFinished(); } private void createNativeTree(VGroup rootGroup) { @@ -840,7 +874,8 @@ public class VectorDrawable extends Drawable { VMRuntime.getRuntime().registerNativeAllocation(NATIVE_ALLOCATION_SIZE); } - + // This should be called every time after a new RootGroup and all its subtrees are created + // (i.e. in constructors of VectorDrawableState and in inflate). void onTreeConstructionFinished() { mRootGroup.setTree(mNativeTree); mAllocationOfAllNodes = mRootGroup.getNativeSize(); @@ -888,11 +923,6 @@ public class VectorDrawable extends Drawable { || super.canApplyTheme(); } - public VectorDrawableState() { - mRootGroup = new VGroup(); - createNativeTree(mRootGroup); - } - @Override public Drawable newDrawable() { return new VectorDrawable(this, null); @@ -972,7 +1002,7 @@ public class VectorDrawable extends Drawable { } static class VGroup extends VObject { - private static final int ROTATE_INDEX = 0; + private static final int ROTATION_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; @@ -983,7 +1013,7 @@ public class VectorDrawable extends Drawable { private static final int NATIVE_ALLOCATION_SIZE = 100; - private static final HashMap<String, Integer> sPropertyMap = + private static final HashMap<String, Integer> sPropertyIndexMap = new HashMap<String, Integer>() { { put("translateX", TRANSLATE_X_INDEX); @@ -992,19 +1022,123 @@ public class VectorDrawable extends Drawable { put("scaleY", SCALE_Y_INDEX); put("pivotX", PIVOT_X_INDEX); put("pivotY", PIVOT_Y_INDEX); - put("rotation", ROTATE_INDEX); + put("rotation", ROTATION_INDEX); } }; static int getPropertyIndex(String propertyName) { - if (sPropertyMap.containsKey(propertyName)) { - return sPropertyMap.get(propertyName); + if (sPropertyIndexMap.containsKey(propertyName)) { + return sPropertyIndexMap.get(propertyName); } else { // property not found return -1; } } + // Below are the Properties that wrap the setters to avoid reflection overhead in animations + private static final Property<VGroup, Float> TRANSLATE_X = + new FloatProperty<VGroup> ("translateX") { + @Override + public void setValue(VGroup object, float value) { + object.setTranslateX(value); + } + + @Override + public Float get(VGroup object) { + return object.getTranslateX(); + } + }; + + private static final Property<VGroup, Float> TRANSLATE_Y = + new FloatProperty<VGroup> ("translateY") { + @Override + public void setValue(VGroup object, float value) { + object.setTranslateY(value); + } + + @Override + public Float get(VGroup object) { + return object.getTranslateY(); + } + }; + + private static final Property<VGroup, Float> SCALE_X = + new FloatProperty<VGroup> ("scaleX") { + @Override + public void setValue(VGroup object, float value) { + object.setScaleX(value); + } + + @Override + public Float get(VGroup object) { + return object.getScaleX(); + } + }; + + private static final Property<VGroup, Float> SCALE_Y = + new FloatProperty<VGroup> ("scaleY") { + @Override + public void setValue(VGroup object, float value) { + object.setScaleY(value); + } + + @Override + public Float get(VGroup object) { + return object.getScaleY(); + } + }; + + private static final Property<VGroup, Float> PIVOT_X = + new FloatProperty<VGroup> ("pivotX") { + @Override + public void setValue(VGroup object, float value) { + object.setPivotX(value); + } + + @Override + public Float get(VGroup object) { + return object.getPivotX(); + } + }; + + private static final Property<VGroup, Float> PIVOT_Y = + new FloatProperty<VGroup> ("pivotY") { + @Override + public void setValue(VGroup object, float value) { + object.setPivotY(value); + } + + @Override + public Float get(VGroup object) { + return object.getPivotY(); + } + }; + + private static final Property<VGroup, Float> ROTATION = + new FloatProperty<VGroup> ("rotation") { + @Override + public void setValue(VGroup object, float value) { + object.setRotation(value); + } + + @Override + public Float get(VGroup object) { + return object.getRotation(); + } + }; + + private static final HashMap<String, Property> sPropertyMap = + new HashMap<String, Property>() { + { + put("translateX", TRANSLATE_X); + put("translateY", TRANSLATE_Y); + put("scaleX", SCALE_X); + put("scaleY", SCALE_Y); + put("pivotX", PIVOT_X); + put("pivotY", PIVOT_Y); + put("rotation", ROTATION); + } + }; // Temp array to store transform values obtained from native. private float[] mTransform; ///////////////////////////////////////////////////// @@ -1060,6 +1194,15 @@ public class VectorDrawable extends Drawable { mNativePtr = nCreateGroup(); } + Property getProperty(String propertyName) { + if (sPropertyMap.containsKey(propertyName)) { + return sPropertyMap.get(propertyName); + } else { + // property not found + return null; + } + } + public String getGroupName() { return mGroupName; } @@ -1107,7 +1250,7 @@ public class VectorDrawable extends Drawable { throw new RuntimeException("Error: inconsistent property count"); } float rotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, - mTransform[ROTATE_INDEX]); + mTransform[ROTATION_INDEX]); float pivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mTransform[PIVOT_X_INDEX]); float pivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, @@ -1293,6 +1436,27 @@ public class VectorDrawable extends Drawable { String mPathName; @Config int mChangingConfigurations; + private static final Property<VPath, PathParser.PathData> PATH_DATA = + new Property<VPath, PathParser.PathData>(PathParser.PathData.class, "pathData") { + @Override + public void set(VPath object, PathParser.PathData data) { + object.setPathData(data); + } + + @Override + public PathParser.PathData get(VPath object) { + return object.getPathData(); + } + }; + + Property getProperty(String propertyName) { + if (PATH_DATA.getName().equals(propertyName)) { + return PATH_DATA; + } + // property not found + return null; + } + public VPath() { // Empty constructor. } @@ -1415,7 +1579,7 @@ public class VectorDrawable extends Drawable { private static final int NATIVE_ALLOCATION_SIZE = 264; // Property map for animatable attributes. - private final static HashMap<String, Integer> sPropertyMap + private final static HashMap<String, Integer> sPropertyIndexMap = new HashMap<String, Integer> () { { put("strokeWidth", STROKE_WIDTH_INDEX); @@ -1429,6 +1593,125 @@ public class VectorDrawable extends Drawable { } }; + // Below are the Properties that wrap the setters to avoid reflection overhead in animations + private static final Property<VFullPath, Float> STROKE_WIDTH = + new FloatProperty<VFullPath> ("strokeWidth") { + @Override + public void setValue(VFullPath object, float value) { + object.setStrokeWidth(value); + } + + @Override + public Float get(VFullPath object) { + return object.getStrokeWidth(); + } + }; + + private static final Property<VFullPath, Integer> STROKE_COLOR = + new IntProperty<VFullPath> ("strokeColor") { + @Override + public void setValue(VFullPath object, int value) { + object.setStrokeColor(value); + } + + @Override + public Integer get(VFullPath object) { + return object.getStrokeColor(); + } + }; + + private static final Property<VFullPath, Float> STROKE_ALPHA = + new FloatProperty<VFullPath> ("strokeAlpha") { + @Override + public void setValue(VFullPath object, float value) { + object.setStrokeAlpha(value); + } + + @Override + public Float get(VFullPath object) { + return object.getStrokeAlpha(); + } + }; + + private static final Property<VFullPath, Integer> FILL_COLOR = + new IntProperty<VFullPath>("fillColor") { + @Override + public void setValue(VFullPath object, int value) { + object.setFillColor(value); + } + + @Override + public Integer get(VFullPath object) { + return object.getFillColor(); + } + }; + + private static final Property<VFullPath, Float> FILL_ALPHA = + new FloatProperty<VFullPath> ("fillAlpha") { + @Override + public void setValue(VFullPath object, float value) { + object.setFillAlpha(value); + } + + @Override + public Float get(VFullPath object) { + return object.getFillAlpha(); + } + }; + + private static final Property<VFullPath, Float> TRIM_PATH_START = + new FloatProperty<VFullPath> ("trimPathStart") { + @Override + public void setValue(VFullPath object, float value) { + object.setTrimPathStart(value); + } + + @Override + public Float get(VFullPath object) { + return object.getTrimPathStart(); + } + }; + + private static final Property<VFullPath, Float> TRIM_PATH_END = + new FloatProperty<VFullPath> ("trimPathEnd") { + @Override + public void setValue(VFullPath object, float value) { + object.setTrimPathEnd(value); + } + + @Override + public Float get(VFullPath object) { + return object.getTrimPathEnd(); + } + }; + + private static final Property<VFullPath, Float> TRIM_PATH_OFFSET = + new FloatProperty<VFullPath> ("trimPathOffset") { + @Override + public void setValue(VFullPath object, float value) { + object.setTrimPathOffset(value); + } + + @Override + public Float get(VFullPath object) { + return object.getTrimPathOffset(); + } + }; + + private final static HashMap<String, Property> sPropertyMap + = new HashMap<String, Property> () { + { + put("strokeWidth", STROKE_WIDTH); + put("strokeColor", STROKE_COLOR); + put("strokeAlpha", STROKE_ALPHA); + put("fillColor", FILL_COLOR); + put("fillAlpha", FILL_ALPHA); + put("trimPathStart", TRIM_PATH_START); + put("trimPathEnd", TRIM_PATH_END); + put("trimPathOffset", TRIM_PATH_OFFSET); + } + }; + // Temp array to store property data obtained from native getter. private byte[] mPropertyData; ///////////////////////////////////////////////////// @@ -1451,11 +1734,24 @@ public class VectorDrawable extends Drawable { mFillColors = copy.mFillColors; } + Property getProperty(String propertyName) { + Property p = super.getProperty(propertyName); + if (p != null) { + return p; + } + if (sPropertyMap.containsKey(propertyName)) { + return sPropertyMap.get(propertyName); + } else { + // property not found + return null; + } + } + int getPropertyIndex(String propertyName) { - if (!sPropertyMap.containsKey(propertyName)) { + if (!sPropertyIndexMap.containsKey(propertyName)) { return -1; } else { - return sPropertyMap.get(propertyName); + return sPropertyIndexMap.get(propertyName); } } @@ -1789,6 +2085,7 @@ public class VectorDrawable extends Drawable { abstract boolean onStateChange(int[] state); abstract boolean isStateful(); abstract int getNativeSize(); + abstract Property getProperty(String propertyName); } private static native long nCreateTree(long rootGroupPtr); |
