diff options
Diffstat (limited to 'graphics')
19 files changed, 1130 insertions, 1369 deletions
diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java index c263a84..a40085b 100644 --- a/graphics/java/android/graphics/Camera.java +++ b/graphics/java/android/graphics/Camera.java @@ -154,7 +154,7 @@ public class Camera { getMatrix(mMatrix); canvas.concat(mMatrix); } else { - nativeApplyToCanvas(canvas.mNativeCanvas); + nativeApplyToCanvas(canvas.getNativeCanvas()); } } diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index ae3eae1..5b18623 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -39,8 +39,12 @@ import javax.microedition.khronos.opengles.GL; public class Canvas { // assigned in constructors or setBitmap, freed in finalizer + private long mNativeCanvas; + /** @hide */ - public long mNativeCanvas; + public long getNativeCanvas() { + return mNativeCanvas; + } // may be null private Bitmap mBitmap; diff --git a/graphics/java/android/graphics/CanvasProperty.java b/graphics/java/android/graphics/CanvasProperty.java new file mode 100644 index 0000000..be86060 --- /dev/null +++ b/graphics/java/android/graphics/CanvasProperty.java @@ -0,0 +1,48 @@ +/* + * 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; + +import com.android.internal.util.VirtualRefBasePtr; + +/** + * TODO: Make public? + * @hide + */ +public final class CanvasProperty<T> { + + private VirtualRefBasePtr mProperty; + + public static CanvasProperty<Float> createFloat(float initialValue) { + return new CanvasProperty<Float>(nCreateFloat(initialValue)); + } + + public static CanvasProperty<Paint> createPaint(Paint initialValue) { + return new CanvasProperty<Paint>(nCreatePaint(initialValue.mNativePaint)); + } + + private CanvasProperty(long nativeContainer) { + mProperty = new VirtualRefBasePtr(nativeContainer); + } + + /** @hide */ + public long getNativeContainer() { + return mProperty.get(); + } + + private static native long nCreateFloat(float initialValue); + private static native long nCreatePaint(long initialValuePaintPtr); +} diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index e08ed50..062acaf 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -253,4 +253,31 @@ public class ImageFormat { } return -1; } + + /** + * Determine whether or not this is a public-visible {@code format}. + * + * <p>In particular, {@code @hide} formats will return {@code false}.</p> + * + * <p>Any other formats (including UNKNOWN) will return {@code false}.</p> + * + * @param format an integer format + * @return a boolean + * + * @hide + */ + public static boolean isPublicFormat(int format) { + switch (format) { + case RGB_565: + case NV16: + case YUY2: + case YV12: + case NV21: + case YUV_420_888: + case RAW_SENSOR: + return true; + } + + return false; + } } diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index 69089b1..6ff5f4f 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -164,12 +164,12 @@ public class NinePatch { } void drawSoftware(Canvas canvas, RectF location, Paint paint) { - nativeDraw(canvas.mNativeCanvas, location, mBitmap.ni(), mNativeChunk, + nativeDraw(canvas.getNativeCanvas(), location, mBitmap.ni(), mNativeChunk, paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity); } void drawSoftware(Canvas canvas, Rect location, Paint paint) { - nativeDraw(canvas.mNativeCanvas, location, mBitmap.ni(), mNativeChunk, + nativeDraw(canvas.getNativeCanvas(), location, mBitmap.ni(), mNativeChunk, paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity); } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index af8b9d9..bdef404 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -55,27 +55,6 @@ public class Paint { /** * @hide */ - public boolean hasShadow; - /** - * @hide - */ - public float shadowDx; - /** - * @hide - */ - public float shadowDy; - /** - * @hide - */ - public float shadowRadius; - /** - * @hide - */ - public int shadowColor; - - /** - * @hide - */ public int mBidiFlags = BIDI_DEFAULT_LTR; static final Style[] sStyleArray = { @@ -492,12 +471,6 @@ public class Paint { mCompatScaling = 1; mInvCompatScaling = 1; - hasShadow = false; - shadowDx = 0; - shadowDy = 0; - shadowRadius = 0; - shadowColor = 0; - mBidiFlags = BIDI_DEFAULT_LTR; setTextLocale(Locale.getDefault()); setElegantTextHeight(false); @@ -538,12 +511,6 @@ public class Paint { mCompatScaling = paint.mCompatScaling; mInvCompatScaling = paint.mInvCompatScaling; - hasShadow = paint.hasShadow; - shadowDx = paint.shadowDx; - shadowDy = paint.shadowDy; - shadowRadius = paint.shadowRadius; - shadowColor = paint.shadowColor; - mBidiFlags = paint.mBidiFlags; mLocale = paint.mLocale; } @@ -1135,22 +1102,24 @@ public class Paint { * layer is removed. */ public void setShadowLayer(float radius, float dx, float dy, int color) { - hasShadow = radius > 0.0f; - shadowRadius = radius; - shadowDx = dx; - shadowDy = dy; - shadowColor = color; - nSetShadowLayer(radius, dx, dy, color); + native_setShadowLayer(mNativePaint, radius, dx, dy, color); } - - private native void nSetShadowLayer(float radius, float dx, float dy, int color); /** * Clear the shadow layer. */ public void clearShadowLayer() { - hasShadow = false; - nSetShadowLayer(0, 0, 0, 0); + setShadowLayer(0, 0, 0, 0); + } + + /** + * Checks if the paint has a shadow layer attached + * + * @return true if the paint has a shadow layer attached and false otherwise + * @hide + */ + public boolean hasShadowLayer() { + return native_hasShadowLayer(mNativePaint); } /** @@ -2295,4 +2264,8 @@ public class Paint { private static native void nativeGetCharArrayBounds(long nativePaint, char[] text, int index, int count, int bidiFlags, Rect bounds); private static native void finalizer(long nativePaint); + + private static native void native_setShadowLayer(long native_object, + float radius, float dx, float dy, int color); + private static native boolean native_hasShadowLayer(long native_object); } diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index 25188e0..a16c099 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -107,7 +107,7 @@ public class Picture { if (mRecordingCanvas != null) { endRecording(); } - nativeDraw(canvas.mNativeCanvas, mNativePicture); + nativeDraw(canvas.getNativeCanvas(), mNativePicture); } /** diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java index d96d6d8..832b9c3 100644 --- a/graphics/java/android/graphics/PixelFormat.java +++ b/graphics/java/android/graphics/PixelFormat.java @@ -115,7 +115,7 @@ public class PixelFormat info.bytesPerPixel = 1; break; default: - throw new IllegalArgumentException("unkonwon pixel format " + format); + throw new IllegalArgumentException("unknown pixel format " + format); } } @@ -135,4 +135,29 @@ public class PixelFormat public int bytesPerPixel; public int bitsPerPixel; + + /** + * Determine whether or not this is a public-visible and non-deprecated {@code format}. + * + * <p>In particular, {@code @hide} formats will return {@code false}.</p> + * + * <p>Any other indirect formats (such as {@code TRANSPARENT} or {@code TRANSLUCENT}) + * will return {@code false}.</p> + * + * @param format an integer format + * @return a boolean + * + * @hide + */ + public static boolean isPublicFormat(int format) { + switch (format) { + case RGBA_8888: + case RGBX_8888: + case RGB_888: + case RGB_565: + return true; + } + + return false; + } } diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index 8b5609f..437d2f4 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -36,9 +36,21 @@ public final class Rect implements Parcelable { public int right; public int bottom; - private static final Pattern FLATTENED_PATTERN = Pattern.compile( + /** + * A helper class for flattened rectange pattern recognition. A separate + * class to avoid an initialization dependency on a regular expression + * causing Rect to not be initializable with an ahead-of-time compilation + * scheme. + */ + private static final class UnflattenHelper { + private static final Pattern FLATTENED_PATTERN = Pattern.compile( "(-?\\d+) (-?\\d+) (-?\\d+) (-?\\d+)"); + static Matcher getMatcher(String str) { + return FLATTENED_PATTERN.matcher(str); + } + } + /** * Create a new empty Rect. All coordinates are initialized to 0. */ @@ -152,7 +164,7 @@ public final class Rect implements Parcelable { * or null if the string is not of that form. */ public static Rect unflattenFromString(String str) { - Matcher matcher = FLATTENED_PATTERN.matcher(str); + Matcher matcher = UnflattenHelper.getMatcher(str); if (!matcher.matches()) { return null; } diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index d877502..0862cdd 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -18,6 +18,7 @@ package android.graphics; import java.lang.ref.WeakReference; +import android.annotation.Nullable; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -62,9 +63,8 @@ import android.view.Surface; * #updateTexImage} should not be called directly from the callback. */ public class SurfaceTexture { - - private EventHandler mEventHandler; - private OnFrameAvailableListener mOnFrameAvailableListener; + private final Looper mCreatorLooper; + private Handler mOnFrameAvailableHandler; /** * These fields are used by native code, do not access or modify. @@ -83,7 +83,8 @@ public class SurfaceTexture { /** * Exception thrown when a SurfaceTexture couldn't be created or resized. * - * @deprecated No longer thrown. {@link Surface.OutOfResourcesException} is used instead. + * @deprecated No longer thrown. {@link android.view.Surface.OutOfResourcesException} + * is used instead. */ @SuppressWarnings("serial") @Deprecated @@ -100,10 +101,10 @@ public class SurfaceTexture { * * @param texName the OpenGL texture object name (e.g. generated via glGenTextures) * - * @throws OutOfResourcesException If the SurfaceTexture cannot be created. + * @throws Surface.OutOfResourcesException If the SurfaceTexture cannot be created. */ public SurfaceTexture(int texName) { - init(texName, false); + this(texName, false); } /** @@ -121,20 +122,59 @@ public class SurfaceTexture { * @param texName the OpenGL texture object name (e.g. generated via glGenTextures) * @param singleBufferMode whether the SurfaceTexture will be in single buffered mode. * - * @throws throws OutOfResourcesException If the SurfaceTexture cannot be created. + * @throws Surface.OutOfResourcesException If the SurfaceTexture cannot be created. */ public SurfaceTexture(int texName, boolean singleBufferMode) { - init(texName, singleBufferMode); + mCreatorLooper = Looper.myLooper(); + nativeInit(texName, singleBufferMode, new WeakReference<SurfaceTexture>(this)); } /** * Register a callback to be invoked when a new image frame becomes available to the - * SurfaceTexture. Note that this callback may be called on an arbitrary thread, so it is not + * SurfaceTexture. + * <p> + * The callback may be called on an arbitrary thread, so it is not * safe to call {@link #updateTexImage} without first binding the OpenGL ES context to the * thread invoking the callback. + * </p> + * + * @param listener The listener to use, or null to remove the listener. */ - public void setOnFrameAvailableListener(OnFrameAvailableListener l) { - mOnFrameAvailableListener = l; + public void setOnFrameAvailableListener(@Nullable OnFrameAvailableListener listener) { + setOnFrameAvailableListener(listener, null); + } + + /** + * Register a callback to be invoked when a new image frame becomes available to the + * SurfaceTexture. + * <p> + * If a handler is specified, the callback will be invoked on that handler's thread. + * If no handler is specified, then the callback may be called on an arbitrary thread, + * so it is not safe to call {@link #updateTexImage} without first binding the OpenGL ES + * context to the thread invoking the callback. + * </p> + * + * @param listener The listener to use, or null to remove the listener. + * @param handler The handler on which the listener should be invoked, or null + * to use an arbitrary thread. + */ + public void setOnFrameAvailableListener(@Nullable final OnFrameAvailableListener listener, + @Nullable Handler handler) { + if (listener != null) { + // Although we claim the thread is arbitrary, earlier implementation would + // prefer to send the callback on the creating looper or the main looper + // so we preserve this behavior here. + Looper looper = handler != null ? handler.getLooper() : + mCreatorLooper != null ? mCreatorLooper : Looper.getMainLooper(); + mOnFrameAvailableHandler = new Handler(looper, null, true /*async*/) { + @Override + public void handleMessage(Message msg) { + listener.onFrameAvailable(SurfaceTexture.this); + } + }; + } else { + mOnFrameAvailableHandler = null; + } } /** @@ -285,49 +325,22 @@ public class SurfaceTexture { } } - private class EventHandler extends Handler { - public EventHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - if (mOnFrameAvailableListener != null) { - mOnFrameAvailableListener.onFrameAvailable(SurfaceTexture.this); - } - } - } - /** * This method is invoked from native code only. */ @SuppressWarnings({"UnusedDeclaration"}) - private static void postEventFromNative(Object selfRef) { - WeakReference weakSelf = (WeakReference)selfRef; - SurfaceTexture st = (SurfaceTexture)weakSelf.get(); - if (st == null) { - return; - } - - if (st.mEventHandler != null) { - Message m = st.mEventHandler.obtainMessage(); - st.mEventHandler.sendMessage(m); - } - } - - private void init(int texName, boolean singleBufferMode) throws Surface.OutOfResourcesException { - Looper looper; - if ((looper = Looper.myLooper()) != null) { - mEventHandler = new EventHandler(looper); - } else if ((looper = Looper.getMainLooper()) != null) { - mEventHandler = new EventHandler(looper); - } else { - mEventHandler = null; + private static void postEventFromNative(WeakReference<SurfaceTexture> weakSelf) { + SurfaceTexture st = weakSelf.get(); + if (st != null) { + Handler handler = st.mOnFrameAvailableHandler; + if (handler != null) { + handler.sendEmptyMessage(0); + } } - nativeInit(texName, singleBufferMode, new WeakReference<SurfaceTexture>(this)); } - private native void nativeInit(int texName, boolean singleBufferMode, Object weakSelf) + private native void nativeInit(int texName, boolean singleBufferMode, + WeakReference<SurfaceTexture> weakSelf) throws Surface.OutOfResourcesException; private native void nativeFinalize(); private native void nativeGetTransformMatrix(float[] mtx); diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java new file mode 100644 index 0000000..46e3401 --- /dev/null +++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java @@ -0,0 +1,524 @@ +/* + * 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.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.LongSparseLongArray; +import android.util.SparseIntArray; +import android.util.StateSet; + +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * Drawable containing a set of Drawable keyframes where the currently displayed + * keyframe is chosen based on the current state set. Animations between + * keyframes may optionally be defined using transition elements. + * <p> + * This drawable can be defined in an XML file with the <code> + * <animated-selector></code> element. Each keyframe Drawable is defined in a + * nested <code><item></code> element. Transitions are defined in a nested + * <code><transition></code> element. + * + * @attr ref android.R.styleable#DrawableStates_state_focused + * @attr ref android.R.styleable#DrawableStates_state_window_focused + * @attr ref android.R.styleable#DrawableStates_state_enabled + * @attr ref android.R.styleable#DrawableStates_state_checkable + * @attr ref android.R.styleable#DrawableStates_state_checked + * @attr ref android.R.styleable#DrawableStates_state_selected + * @attr ref android.R.styleable#DrawableStates_state_activated + * @attr ref android.R.styleable#DrawableStates_state_active + * @attr ref android.R.styleable#DrawableStates_state_single + * @attr ref android.R.styleable#DrawableStates_state_first + * @attr ref android.R.styleable#DrawableStates_state_middle + * @attr ref android.R.styleable#DrawableStates_state_last + * @attr ref android.R.styleable#DrawableStates_state_pressed + */ +public class AnimatedStateListDrawable extends StateListDrawable { + private static final String ELEMENT_TRANSITION = "transition"; + private static final String ELEMENT_ITEM = "item"; + + private AnimatedStateListState mState; + + /** The currently running animation, if any. */ + private ObjectAnimator mAnim; + + /** Index to be set after the animation ends. */ + private int mAnimToIndex = -1; + + /** Index away from which we are animating. */ + private int mAnimFromIndex = -1; + + private boolean mMutated; + + public AnimatedStateListDrawable() { + this(null, null); + } + + /** + * Add a new drawable to the set of keyframes. + * + * @param stateSet An array of resource IDs to associate with the keyframe + * @param drawable The drawable to show when in the specified state + * @param id The unique identifier for the keyframe + */ + public void addState(int[] stateSet, Drawable drawable, int id) { + if (drawable != null) { + mState.addStateSet(stateSet, drawable, id); + onStateChange(getState()); + } + } + + /** + * Adds a new transition between keyframes. + * + * @param fromId Unique identifier of the starting keyframe + * @param toId Unique identifier of the ending keyframe + * @param anim An AnimationDrawable to use as a transition + * @param reversible Whether the transition can be reversed + */ + public void addTransition(int fromId, int toId, AnimationDrawable anim, boolean reversible) { + mState.addTransition(fromId, toId, anim, reversible); + } + + @Override + public boolean isStateful() { + return true; + } + + @Override + protected boolean onStateChange(int[] stateSet) { + final int keyframeIndex = mState.indexOfKeyframe(stateSet); + if (keyframeIndex == getCurrentIndex()) { + return false; + } + + if (selectTransition(keyframeIndex)) { + return true; + } + + if (selectDrawable(keyframeIndex)) { + return true; + } + + return super.onStateChange(stateSet); + } + + private boolean selectTransition(int toIndex) { + if (mAnim != null) { + if (toIndex == mAnimToIndex) { + // Already animating to that keyframe. + return true; + } else if (toIndex == mAnimFromIndex) { + // Reverse the current animation. + mAnim.reverse(); + mAnimFromIndex = mAnimToIndex; + mAnimToIndex = toIndex; + return true; + } + + // Changing animation, end the current animation. + mAnim.end(); + } + + final AnimatedStateListState state = mState; + final int fromIndex = getCurrentIndex(); + final int fromId = state.getKeyframeIdAt(fromIndex); + final int toId = state.getKeyframeIdAt(toIndex); + + if (toId == 0 || fromId == 0) { + // Missing a keyframe ID. + return false; + } + + final int transitionIndex = state.indexOfTransition(fromId, toId); + if (transitionIndex < 0 || !selectDrawable(transitionIndex)) { + // Couldn't select a transition. + return false; + } + + final Drawable d = getCurrent(); + if (!(d instanceof AnimationDrawable)) { + // Transition isn't an animation. + return false; + } + + final AnimationDrawable ad = (AnimationDrawable) d; + final boolean reversed = mState.isTransitionReversed(fromId, toId); + final int frameCount = ad.getNumberOfFrames(); + final int fromFrame = reversed ? frameCount - 1 : 0; + final int toFrame = reversed ? 0 : frameCount - 1; + + final FrameInterpolator interp = new FrameInterpolator(ad, reversed); + final ObjectAnimator anim = ObjectAnimator.ofInt(ad, "currentIndex", fromFrame, toFrame); + anim.setAutoCancel(true); + anim.setDuration(interp.getTotalDuration()); + anim.addListener(mAnimListener); + anim.setInterpolator(interp); + anim.start(); + + mAnim = anim; + mAnimFromIndex = fromIndex; + mAnimToIndex = toIndex; + return true; + } + + @Override + public void jumpToCurrentState() { + super.jumpToCurrentState(); + + if (mAnim != null) { + mAnim.end(); + } + } + + @Override + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + final TypedArray a = r.obtainAttributes(attrs, R.styleable.AnimatedStateListDrawable); + + super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedStateListDrawable_visible); + + final StateListState stateListState = getStateListState(); + stateListState.setVariablePadding(a.getBoolean( + R.styleable.AnimatedStateListDrawable_variablePadding, false)); + stateListState.setConstantSize(a.getBoolean( + R.styleable.AnimatedStateListDrawable_constantSize, false)); + stateListState.setEnterFadeDuration(a.getInt( + R.styleable.AnimatedStateListDrawable_enterFadeDuration, 0)); + stateListState.setExitFadeDuration(a.getInt( + R.styleable.AnimatedStateListDrawable_exitFadeDuration, 0)); + + setDither(a.getBoolean(R.styleable.AnimatedStateListDrawable_dither, true)); + setAutoMirrored(a.getBoolean(R.styleable.AnimatedStateListDrawable_autoMirrored, false)); + + a.recycle(); + + int type; + + final int innerDepth = parser.getDepth() + 1; + int depth; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth + || type != XmlPullParser.END_TAG)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + + if (depth > innerDepth) { + continue; + } + + if (parser.getName().equals(ELEMENT_ITEM)) { + parseItem(r, parser, attrs, theme); + } else if (parser.getName().equals(ELEMENT_TRANSITION)) { + parseTransition(r, parser, attrs, theme); + } + } + + onStateChange(getState()); + } + + private int parseTransition(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + int drawableRes = 0; + int fromId = 0; + int toId = 0; + boolean reversible = false; + + final int numAttrs = attrs.getAttributeCount(); + for (int i = 0; i < numAttrs; i++) { + final int stateResId = attrs.getAttributeNameResource(i); + switch (stateResId) { + case 0: + break; + case R.attr.fromId: + fromId = attrs.getAttributeResourceValue(i, 0); + break; + case R.attr.toId: + toId = attrs.getAttributeResourceValue(i, 0); + break; + case R.attr.drawable: + drawableRes = attrs.getAttributeResourceValue(i, 0); + break; + case R.attr.reversible: + reversible = attrs.getAttributeBooleanValue(i, false); + break; + } + } + + final Drawable dr; + if (drawableRes != 0) { + dr = r.getDrawable(drawableRes); + } else { + int type; + while ((type = parser.next()) == XmlPullParser.TEXT) { + } + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException( + parser.getPositionDescription() + + ": <item> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); + } + + final AnimationDrawable anim; + if (dr instanceof AnimationDrawable) { + anim = (AnimationDrawable) dr; + } else { + throw new XmlPullParserException(parser.getPositionDescription() + + ": <transition> tag requires a 'drawable' attribute or " + + "child tag defining a drawable of type <animation>"); + } + + return mState.addTransition(fromId, toId, anim, reversible); + } + + private int parseItem(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + int drawableRes = 0; + int keyframeId = 0; + + int j = 0; + final int numAttrs = attrs.getAttributeCount(); + int[] states = new int[numAttrs]; + for (int i = 0; i < numAttrs; i++) { + final int stateResId = attrs.getAttributeNameResource(i); + switch (stateResId) { + case 0: + break; + case R.attr.id: + keyframeId = attrs.getAttributeResourceValue(i, 0); + break; + case R.attr.drawable: + drawableRes = attrs.getAttributeResourceValue(i, 0); + break; + default: + final boolean hasState = attrs.getAttributeBooleanValue(i, false); + states[j++] = hasState ? stateResId : -stateResId; + } + } + states = StateSet.trimStateSet(states, j); + + final Drawable dr; + if (drawableRes != 0) { + dr = r.getDrawable(drawableRes); + } else { + int type; + while ((type = parser.next()) == XmlPullParser.TEXT) { + } + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException( + parser.getPositionDescription() + + ": <item> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); + } + + return mState.addStateSet(states, dr, keyframeId); + } + + @Override + public Drawable mutate() { + if (!mMutated) { + final AnimatedStateListState newState = new AnimatedStateListState(mState, this, null); + setConstantState(newState); + mMutated = true; + } + + return this; + } + + private final AnimatorListenerAdapter mAnimListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator anim) { + selectDrawable(mAnimToIndex); + + mAnimToIndex = -1; + mAnimFromIndex = -1; + mAnim = null; + } + }; + + static class AnimatedStateListState extends StateListState { + private static final int REVERSE_SHIFT = 32; + private static final int REVERSE_MASK = 0x1; + + final LongSparseLongArray mTransitions; + final SparseIntArray mStateIds; + + AnimatedStateListState(AnimatedStateListState orig, AnimatedStateListDrawable owner, + Resources res) { + super(orig, owner, res); + + if (orig != null) { + mTransitions = orig.mTransitions.clone(); + mStateIds = orig.mStateIds.clone(); + } else { + mTransitions = new LongSparseLongArray(); + mStateIds = new SparseIntArray(); + } + } + + int addTransition(int fromId, int toId, AnimationDrawable anim, boolean reversible) { + final int pos = super.addChild(anim); + final long keyFromTo = generateTransitionKey(fromId, toId); + mTransitions.append(keyFromTo, pos); + + if (reversible) { + final long keyToFrom = generateTransitionKey(toId, fromId); + mTransitions.append(keyToFrom, pos | (1L << REVERSE_SHIFT)); + } + + return addChild(anim); + } + + int addStateSet(int[] stateSet, Drawable drawable, int id) { + final int index = super.addStateSet(stateSet, drawable); + mStateIds.put(index, id); + return index; + } + + int indexOfKeyframe(int[] stateSet) { + final int index = super.indexOfStateSet(stateSet); + if (index >= 0) { + return index; + } + + return super.indexOfStateSet(StateSet.WILD_CARD); + } + + int getKeyframeIdAt(int index) { + return index < 0 ? 0 : mStateIds.get(index, 0); + } + + int indexOfTransition(int fromId, int toId) { + final long keyFromTo = generateTransitionKey(fromId, toId); + return (int) mTransitions.get(keyFromTo, -1); + } + + boolean isTransitionReversed(int fromId, int toId) { + final long keyFromTo = generateTransitionKey(fromId, toId); + return (mTransitions.get(keyFromTo, -1) >> REVERSE_SHIFT & REVERSE_MASK) == 1; + } + + @Override + public Drawable newDrawable() { + return new AnimatedStateListDrawable(this, null); + } + + @Override + public Drawable newDrawable(Resources res) { + return new AnimatedStateListDrawable(this, res); + } + + private static long generateTransitionKey(int fromId, int toId) { + return (long) fromId << 32 | toId; + } + } + + void setConstantState(AnimatedStateListState state) { + super.setConstantState(state); + + mState = state; + } + + private AnimatedStateListDrawable(AnimatedStateListState state, Resources res) { + super(null); + + final AnimatedStateListState newState = new AnimatedStateListState(state, this, res); + setConstantState(newState); + onStateChange(getState()); + jumpToCurrentState(); + } + + /** + * Interpolates between frames with respect to their individual durations. + */ + private static class FrameInterpolator implements TimeInterpolator { + private int[] mFrameTimes; + private int mFrames; + private int mTotalDuration; + + public FrameInterpolator(AnimationDrawable d, boolean reversed) { + updateFrames(d, reversed); + } + + public int updateFrames(AnimationDrawable d, boolean reversed) { + final int N = d.getNumberOfFrames(); + mFrames = N; + + if (mFrameTimes == null || mFrameTimes.length < N) { + mFrameTimes = new int[N]; + } + + final int[] frameTimes = mFrameTimes; + int totalDuration = 0; + for (int i = 0; i < N; i++) { + final int duration = d.getDuration(reversed ? N - i - 1 : i); + frameTimes[i] = duration; + totalDuration += duration; + } + + mTotalDuration = totalDuration; + return totalDuration; + } + + public int getTotalDuration() { + return mTotalDuration; + } + + @Override + public float getInterpolation(float input) { + final int elapsed = (int) (input * mTotalDuration + 0.5f); + final int N = mFrames; + final int[] frameTimes = mFrameTimes; + + // Find the current frame and remaining time within that frame. + int remaining = elapsed; + int i = 0; + while (i < N && remaining >= frameTimes[i]) { + remaining -= frameTimes[i]; + i++; + } + + // Remaining time is relative of total duration. + final float frameElapsed; + if (i < N) { + frameElapsed = remaining / (float) mTotalDuration; + } else { + frameElapsed = 0; + } + + return i / (float) N + frameElapsed; + } + } +} + diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index 3f94e26..da4bc10 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -94,7 +94,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An boolean changed = super.setVisible(visible, restart); if (visible) { if (changed || restart) { - setFrame(0, true, true); + setFrame(0, true, mCurFrame >= 0); } } else { unscheduleSelf(this); diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index b9d5e19..b939636 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -1039,6 +1039,8 @@ public abstract class Drawable { final String name = parser.getName(); if (name.equals("selector")) { drawable = new StateListDrawable(); + } else if (name.equals("animated-selector")) { + drawable = new AnimatedStateListDrawable(); } else if (name.equals("level-list")) { drawable = new LevelListDrawable(); } else if (name.equals("layer-list")) { diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index 1f8b51d..08fc99d 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -359,6 +359,16 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mDrawableContainerState.getOpacity(); } + /** @hide */ + public void setCurrentIndex(int index) { + selectDrawable(index); + } + + /** @hide */ + public int getCurrentIndex() { + return mCurIndex; + } + public boolean selectDrawable(int idx) { if (idx == mCurIndex) { return false; diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java index e3f57e9..218a057 100644 --- a/graphics/java/android/graphics/drawable/Ripple.java +++ b/graphics/java/android/graphics/drawable/Ripple.java @@ -16,20 +16,22 @@ package android.graphics.drawable; +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; -import android.util.MathUtils; -import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; /** * Draws a Quantum Paper ripple. */ class Ripple { - private static final TimeInterpolator INTERPOLATOR = new DecelerateInterpolator(2.0f); + private static final TimeInterpolator INTERPOLATOR = new DecelerateInterpolator(); /** Starting radius for a ripple. */ private static final int STARTING_RADIUS_DP = 16; @@ -37,6 +39,9 @@ class Ripple { /** Radius when finger is outside view bounds. */ private static final int OUTSIDE_RADIUS_DP = 16; + /** Radius when finger is inside view bounds. */ + private static final int INSIDE_RADIUS_DP = 96; + /** Margin when constraining outside touches (fraction of outer radius). */ private static final float OUTSIDE_MARGIN = 0.8f; @@ -44,15 +49,53 @@ class Ripple { private static final float OUTSIDE_RESISTANCE = 0.7f; /** Minimum alpha value during a pulse animation. */ - private static final int PULSE_MIN_ALPHA = 128; + private static final float PULSE_MIN_ALPHA = 0.5f; + + /** Duration for animating the trailing edge of the ripple. */ + private static final int EXIT_DURATION = 600; + + /** Duration for animating the leading edge of the ripple. */ + private static final int ENTER_DURATION = 400; + + /** Duration for animating the ripple alpha in and out. */ + private static final int FADE_DURATION = 50; + + /** Minimum elapsed time between start of enter and exit animations. */ + private static final int EXIT_MIN_DELAY = 200; + + /** Duration for animating between inside and outside touch. */ + private static final int OUTSIDE_DURATION = 300; + + /** Duration for animating pulses. */ + private static final int PULSE_DURATION = 400; + /** Interval between pulses while inside and fully entered. */ + private static final int PULSE_INTERVAL = 400; + + /** Delay before pulses start. */ + private static final int PULSE_DELAY = 500; + + private final Drawable mOwner; + + /** Bounds used for computing max radius and containment. */ private final Rect mBounds; - private final Rect mPadding; - private RippleAnimator mAnimator; + /** Configured maximum ripple radius when the center is outside the bounds. */ + private final int mMaxOutsideRadius; + + /** Configured maximum ripple radius. */ + private final int mMaxInsideRadius; - private int mMinRadius; - private int mOutsideRadius; + private ObjectAnimator mOuter; + private ObjectAnimator mInner; + private ObjectAnimator mAlpha; + + /** Maximum ripple radius. */ + private int mMaxRadius; + + private float mOuterRadius; + private float mInnerRadius; + private float mAlphaMultiplier; /** Center x-coordinate. */ private float mX; @@ -61,272 +104,297 @@ class Ripple { private float mY; /** Whether the center is within the parent bounds. */ - private boolean mInside; + private boolean mInsideBounds; /** Whether to pulse this ripple. */ - boolean mPulse; - - /** Enter state. A value in [0...1] or -1 if not set. */ - float mEnterState = -1; - - /** Exit state. A value in [0...1] or -1 if not set. */ - float mExitState = -1; + private boolean mPulseEnabled; - /** Outside state. A value in [0...1] or -1 if not set. */ - float mOutsideState = -1; + /** Temporary hack since we can't check finished state of animator. */ + private boolean mExitFinished; - /** Pulse state. A value in [0...1] or -1 if not set. */ - float mPulseState = -1; + /** Whether this ripple has ever moved. */ + private boolean mHasMoved; /** - * Creates a new ripple with the specified parent bounds, padding, initial - * position, and screen density. + * Creates a new ripple. */ - public Ripple(Rect bounds, Rect padding, float x, float y, float density, boolean pulse) { + public Ripple(Drawable owner, Rect bounds, float density, boolean pulseEnabled) { + mOwner = owner; mBounds = bounds; - mPadding = padding; - mInside = mBounds.contains((int) x, (int) y); - mPulse = pulse; + mPulseEnabled = pulseEnabled; - mX = x; - mY = y; + mOuterRadius = (int) (density * STARTING_RADIUS_DP + 0.5f); + mMaxOutsideRadius = (int) (density * OUTSIDE_RADIUS_DP + 0.5f); + mMaxInsideRadius = (int) (density * INSIDE_RADIUS_DP + 0.5f); + mMaxRadius = Math.min(mMaxInsideRadius, Math.max(bounds.width(), bounds.height())); + } + + public void setOuterRadius(float r) { + mOuterRadius = r; + invalidateSelf(); + } + + public float getOuterRadius() { + return mOuterRadius; + } - mMinRadius = (int) (density * STARTING_RADIUS_DP + 0.5f); - mOutsideRadius = (int) (density * OUTSIDE_RADIUS_DP + 0.5f); + public void setInnerRadius(float r) { + mInnerRadius = r; + invalidateSelf(); } - - public void setMinRadius(int minRadius) { - mMinRadius = minRadius; + + public float getInnerRadius() { + return mInnerRadius; + } + + public void setAlphaMultiplier(float a) { + mAlphaMultiplier = a; + invalidateSelf(); } - - public void setOutsideRadius(int outsideRadius) { - mOutsideRadius = outsideRadius; + + public float getAlphaMultiplier() { + return mAlphaMultiplier; } /** - * Updates the center coordinates. + * Returns whether this ripple has finished exiting. */ - public void move(float x, float y) { - mX = x; - mY = y; - - final boolean inside = mBounds.contains((int) x, (int) y); - if (mInside != inside) { - if (mAnimator != null) { - mAnimator.outside(); - } - mInside = inside; - } + public boolean isFinished() { + return mExitFinished; } + /** + * Called when the bounds change. + */ public void onBoundsChanged() { - final boolean inside = mBounds.contains((int) mX, (int) mY); - if (mInside != inside) { - if (mAnimator != null) { - mAnimator.outside(); - } - mInside = inside; - } + mMaxRadius = Math.min(mMaxInsideRadius, Math.max(mBounds.width(), mBounds.height())); + + updateInsideBounds(); } - public RippleAnimator animate() { - if (mAnimator == null) { - mAnimator = new RippleAnimator(this); + private void updateInsideBounds() { + final boolean insideBounds = mBounds.contains((int) (mX + 0.5f), (int) (mY + 0.5f)); + if (mInsideBounds != insideBounds || !mHasMoved) { + mInsideBounds = insideBounds; + mHasMoved = true; + + if (insideBounds) { + enter(); + } else { + outside(); + } } - return mAnimator; } + /** + * Draws the ripple using the specified paint. + */ public boolean draw(Canvas c, Paint p) { final Rect bounds = mBounds; - final Rect padding = mPadding; - final float dX = Math.max(mX - bounds.left, bounds.right - mX); - final float dY = Math.max(mY - bounds.top, bounds.bottom - mY); - final int maxRadius = (int) Math.ceil(Math.sqrt(dX * dX + dY * dY)); - - final float enterState = mEnterState; - final float exitState = mExitState; - final float outsideState = mOutsideState; - final float pulseState = mPulseState; - final float insideRadius = MathUtils.lerp(mMinRadius, maxRadius, enterState); - final float outerRadius = MathUtils.lerp(mOutsideRadius, insideRadius, - mInside ? outsideState : 1 - outsideState); + final float outerRadius = mOuterRadius; + final float innerRadius = mInnerRadius; + final float alphaMultiplier = mAlphaMultiplier; + + // Cache the paint alpha so we can restore it later. + final int paintAlpha = p.getAlpha(); + final int alpha = (int) (paintAlpha * alphaMultiplier + 0.5f); // Apply resistance effect when outside bounds. - final float x = looseConstrain(mX, bounds.left + padding.left, bounds.right - padding.right, - outerRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE); - final float y = looseConstrain(mY, bounds.top + padding.top, bounds.bottom - padding.bottom, - outerRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE); - - // Compute maximum alpha, taking pulse into account when active. - final int maxAlpha; - if (pulseState < 0 || pulseState >= 1) { - maxAlpha = 255; + final float x; + final float y; + if (mInsideBounds) { + x = mX; + y = mY; } else { - final float pulseAlpha; - if (pulseState > 0.5) { - // Pulsing in to max alpha. - pulseAlpha = MathUtils.lerp(PULSE_MIN_ALPHA, 255, (pulseState - .5f) * 2); - } else { - // Pulsing out to min alpha. - pulseAlpha = MathUtils.lerp(255, PULSE_MIN_ALPHA, pulseState * 2f); - } - - if (exitState > 0) { - // Animating exit, interpolate pulse with exit state. - maxAlpha = (int) (MathUtils.lerp(255, pulseAlpha, exitState) + 0.5f); - } else if (mInside) { - // No animation, no need to interpolate. - maxAlpha = (int) (pulseAlpha + 0.5f); - } else { - // Animating inside, interpolate pulse with inside state. - maxAlpha = (int) (MathUtils.lerp(pulseAlpha, 255, outsideState) + 0.5f); - } + // TODO: We need to do this outside of draw() so that our dirty + // bounds accurately reflect resistance. + x = looseConstrain(mX, bounds.left, bounds.right, + mOuterRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE); + y = looseConstrain(mY, bounds.top, bounds.bottom, + mOuterRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE); } - if (maxAlpha > 0) { - if (exitState <= 0) { - // Exit state isn't showing, so we can simplify to a solid - // circle. - if (outerRadius > 0) { - p.setAlpha(maxAlpha); - p.setStyle(Style.FILL); - c.drawCircle(x, y, outerRadius, p); - return true; - } - } else { - // Both states are showing, so we need a circular stroke. - final float innerRadius = MathUtils.lerp(0, outerRadius, exitState); - final float strokeWidth = outerRadius - innerRadius; - if (strokeWidth > 0) { - final float strokeRadius = innerRadius + strokeWidth / 2f; - final int alpha = (int) (MathUtils.lerp(maxAlpha, 0, exitState) + 0.5f); - if (alpha > 0) { - p.setAlpha(alpha); - p.setStyle(Style.STROKE); - p.setStrokeWidth(strokeWidth); - c.drawCircle(x, y, strokeRadius, p); - return true; - } - } - } + final boolean hasContent; + if (alphaMultiplier <= 0 || innerRadius >= outerRadius) { + // Nothing to draw. + hasContent = false; + } else if (innerRadius > 0) { + // Draw a ring. + final float strokeWidth = outerRadius - innerRadius; + final float strokeRadius = innerRadius + strokeWidth / 2.0f; + p.setAlpha(alpha); + p.setStyle(Style.STROKE); + p.setStrokeWidth(strokeWidth); + c.drawCircle(x, y, strokeRadius, p); + hasContent = true; + } else if (outerRadius > 0) { + // Draw a circle. + p.setAlpha(alpha); + p.setStyle(Style.FILL); + c.drawCircle(x, y, outerRadius, p); + hasContent = true; + } else { + hasContent = false; } - return false; + p.setAlpha(paintAlpha); + return hasContent; } + /** + * Returns the maximum bounds for this ripple. + */ public void getBounds(Rect bounds) { final int x = (int) mX; final int y = (int) mY; - final int dX = Math.max(x, mBounds.right - x); - final int dY = Math.max(x, mBounds.bottom - y); - final int maxRadius = (int) Math.ceil(Math.sqrt(dX * dX + dY * dY)); + final int maxRadius = mMaxRadius; bounds.set(x - maxRadius, y - maxRadius, x + maxRadius, y + maxRadius); } /** - * Constrains a value within a specified asymptotic margin outside a minimum - * and maximum. + * Updates the center coordinates. */ - private static float looseConstrain(float value, float min, float max, float margin, - float factor) { - if (value < min) { - return min - Math.min(margin, (float) Math.pow(min - value, factor)); - } else if (value > max) { - return max + Math.min(margin, (float) Math.pow(value - max, factor)); - } else { - return value; - } - } - - public static class RippleAnimator { - /** Duration for animating the trailing edge of the ripple. */ - private static final int EXIT_DURATION = 600; - - /** Duration for animating the leading edge of the ripple. */ - private static final int ENTER_DURATION = 400; - - /** Minimum elapsed time between start of enter and exit animations. */ - private static final int EXIT_MIN_DELAY = 200; - - /** Duration for animating between inside and outside touch. */ - private static final int OUTSIDE_DURATION = 300; - - /** Duration for animating pulses. */ - private static final int PULSE_DURATION = 400; - - /** Interval between pulses while inside and fully entered. */ - private static final int PULSE_INTERVAL = 400; + public void move(float x, float y) { + mX = x; + mY = y; - /** Delay before pulses start. */ - private static final int PULSE_DELAY = 500; + updateInsideBounds(); + invalidateSelf(); + } - /** The target ripple being animated. */ - private final Ripple mTarget; + /** + * Starts the exit animation. If {@link #enter()} was called recently, the + * animation may be postponed. + */ + public void exit() { + mExitFinished = false; + + final ObjectAnimator inner = ObjectAnimator.ofFloat(this, "innerRadius", 0, mMaxRadius); + inner.setAutoCancel(true); + inner.setDuration(EXIT_DURATION); + inner.setInterpolator(INTERPOLATOR); + inner.addListener(mAnimationListener); + + if (mOuter != null && mOuter.isStarted()) { + // If we haven't been running the enter animation for long enough, + // delay the exit animator. + final int elapsed = (int) (mOuter.getAnimatedFraction() * mOuter.getDuration()); + final int delay = Math.max(0, EXIT_MIN_DELAY - elapsed); + inner.setStartDelay(delay); + } - /** When the ripple started appearing. */ - private long mEnterTime = -1; + inner.start(); - /** When the ripple started vanishing. */ - private long mExitTime = -1; + final ObjectAnimator alpha = ObjectAnimator.ofFloat(this, "alphaMultiplier", 0); + alpha.setAutoCancel(true); + alpha.setDuration(EXIT_DURATION); + alpha.start(); - /** When the ripple last transitioned between inside and outside touch. */ - private long mOutsideTime = -1; + mInner = inner; + mAlpha = alpha; + } - public RippleAnimator(Ripple target) { - mTarget = target; + /** + * Cancel all animations. + */ + public void cancel() { + if (mInner != null) { + mInner.cancel(); } - /** - * Starts the enter animation. - */ - public void enter() { - mEnterTime = AnimationUtils.currentAnimationTimeMillis(); + if (mOuter != null) { + mOuter.cancel(); } - /** - * Starts the exit animation. If {@link #enter()} was called recently, the - * animation may be postponed. - */ - public void exit() { - final long minTime = mEnterTime + EXIT_MIN_DELAY; - mExitTime = Math.max(minTime, AnimationUtils.currentAnimationTimeMillis()); + if (mAlpha != null) { + mAlpha.cancel(); } + } + + private void invalidateSelf() { + mOwner.invalidateSelf(); + } - /** - * Starts the outside transition animation. - */ - public void outside() { - mOutsideTime = AnimationUtils.currentAnimationTimeMillis(); + /** + * Starts the enter animation. + */ + private void enter() { + final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerRadius", mMaxRadius); + outer.setAutoCancel(true); + outer.setDuration(ENTER_DURATION); + outer.setInterpolator(INTERPOLATOR); + outer.start(); + + final ObjectAnimator alpha = ObjectAnimator.ofFloat(this, "alphaMultiplier", 1); + if (mPulseEnabled) { + alpha.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + final ObjectAnimator pulse = ObjectAnimator.ofFloat( + this, "alphaMultiplier", 1, PULSE_MIN_ALPHA); + pulse.setAutoCancel(true); + pulse.setDuration(PULSE_DURATION + PULSE_INTERVAL); + pulse.setRepeatCount(ObjectAnimator.INFINITE); + pulse.setRepeatMode(ObjectAnimator.REVERSE); + pulse.setStartDelay(PULSE_DELAY); + pulse.start(); + + mAlpha = pulse; + } + }); } + alpha.setAutoCancel(true); + alpha.setDuration(FADE_DURATION); + alpha.start(); - /** - * Returns whether this ripple is currently animating. - */ - public boolean isRunning() { - final long currentTime = AnimationUtils.currentAnimationTimeMillis(); - return mEnterTime >= 0 && mEnterTime <= currentTime - && (mExitTime < 0 || currentTime <= mExitTime + EXIT_DURATION); + mOuter = outer; + mAlpha = alpha; + } + + /** + * Starts the outside transition animation. + */ + private void outside() { + final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerRadius", mMaxOutsideRadius); + outer.setAutoCancel(true); + outer.setDuration(OUTSIDE_DURATION); + outer.setInterpolator(INTERPOLATOR); + outer.start(); + + final ObjectAnimator alpha = ObjectAnimator.ofFloat(this, "alphaMultiplier", 1); + alpha.setAutoCancel(true); + alpha.setDuration(FADE_DURATION); + alpha.start(); + + mOuter = outer; + mAlpha = alpha; + } + + /** + * Constrains a value within a specified asymptotic margin outside a minimum + * and maximum. + */ + private static float looseConstrain(float value, float min, float max, float margin, + float factor) { + // TODO: Can we use actual spring physics here? + if (value < min) { + return min - Math.min(margin, (float) Math.pow(min - value, factor)); + } else if (value > max) { + return max + Math.min(margin, (float) Math.pow(value - max, factor)); + } else { + return value; } + } - public void update() { - // Track three states: - // - Enter: touch begins, affects outer radius - // - Outside: touch moves outside bounds, affects maximum outer radius - // - Exit: touch ends, affects inner radius - final long currentTime = AnimationUtils.currentAnimationTimeMillis(); - mTarget.mEnterState = mEnterTime < 0 ? 0 : INTERPOLATOR.getInterpolation( - MathUtils.constrain((currentTime - mEnterTime) / (float) ENTER_DURATION, 0, 1)); - mTarget.mExitState = mExitTime < 0 ? 0 : INTERPOLATOR.getInterpolation( - MathUtils.constrain((currentTime - mExitTime) / (float) EXIT_DURATION, 0, 1)); - mTarget.mOutsideState = mOutsideTime < 0 ? 1 : INTERPOLATOR.getInterpolation( - MathUtils.constrain((currentTime - mOutsideTime) / (float) OUTSIDE_DURATION, 0, 1)); - - // Pulse is a little more complicated. - if (mTarget.mPulse) { - final long pulseTime = (currentTime - mEnterTime - ENTER_DURATION - PULSE_DELAY); - mTarget.mPulseState = pulseTime < 0 ? -1 - : (pulseTime % (PULSE_INTERVAL + PULSE_DURATION)) / (float) PULSE_DURATION; + private final AnimatorListener mAnimationListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation == mInner) { + mExitFinished = true; + mOuterRadius = 0; + mInnerRadius = 0; + mAlphaMultiplier = 1; } } - } + }; } diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java index 61b1b85..99ab4dd 100644 --- a/graphics/java/android/graphics/drawable/ShapeDrawable.java +++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java @@ -237,7 +237,7 @@ public class ShapeDrawable extends Drawable { paint.setAlpha(modulateAlpha(prevAlpha, state.mAlpha)); // only draw shape if it may affect output - if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadow) { + if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadowLayer()) { final boolean clearColorFilter; if (mTintFilter != null && paint.getColorFilter() == null) { paint.setColorFilter(mTintFilter); diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java index 271af2b..f22a063 100644 --- a/graphics/java/android/graphics/drawable/StateListDrawable.java +++ b/graphics/java/android/graphics/drawable/StateListDrawable.java @@ -55,8 +55,9 @@ import android.util.StateSet; * @attr ref android.R.styleable#DrawableStates_state_pressed */ public class StateListDrawable extends DrawableContainer { + private static final String TAG = StateListDrawable.class.getSimpleName(); + private static final boolean DEBUG = false; - private static final String TAG = "StateListDrawable"; /** * To be proper, we should have a getter for dither (and alpha, etc.) @@ -69,7 +70,8 @@ public class StateListDrawable extends DrawableContainer { * to improve the quality at negligible cost. */ private static final boolean DEFAULT_DITHER = true; - private final StateListState mStateListState; + + private StateListState mStateListState; private boolean mMutated; public StateListDrawable() { @@ -274,7 +276,7 @@ public class StateListDrawable extends DrawableContainer { mStateListState.setLayoutDirection(layoutDirection); } - static final class StateListState extends DrawableContainerState { + static class StateListState extends DrawableContainerState { int[][] mStateSets; StateListState(StateListState orig, StateListDrawable owner, Resources res) { @@ -293,7 +295,7 @@ public class StateListDrawable extends DrawableContainer { return pos; } - private int indexOfStateSet(int[] stateSet) { + int indexOfStateSet(int[] stateSet) { final int[][] stateSets = mStateSets; final int N = getChildCount(); for (int i = 0; i < N; i++) { @@ -323,11 +325,26 @@ public class StateListDrawable extends DrawableContainer { } } + void setConstantState(StateListState state) { + super.setConstantState(state); + + mStateListState = state; + } + private StateListDrawable(StateListState state, Resources res) { - StateListState as = new StateListState(state, this, res); - mStateListState = as; - setConstantState(as); + final StateListState newState = new StateListState(state, this, res); + setConstantState(newState); onStateChange(getState()); } + + /** + * This constructor exists so subclasses can avoid calling the default + * constructor and setting up a StateListDrawable-specific constant state. + */ + StateListDrawable(StateListState state) { + if (state != null) { + setConstantState(state); + } + } } diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java index 0e8831f..0097183 100644 --- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java +++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java @@ -27,8 +27,6 @@ import android.graphics.PixelFormat; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.drawable.Ripple.RippleAnimator; -import android.os.SystemClock; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -40,7 +38,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.util.Arrays; /** * Documentation pending. @@ -54,7 +51,6 @@ public class TouchFeedbackDrawable extends LayerDrawable { private static final int MAX_RIPPLES = 10; private final Rect mTempRect = new Rect(); - private final Rect mPaddingRect = new Rect(); /** Current ripple effect bounds, used to constrain ripple effects. */ private final Rect mHotspotBounds = new Rect(); @@ -68,14 +64,11 @@ public class TouchFeedbackDrawable extends LayerDrawable { private final TouchFeedbackState mState; /** Lazily-created map of touch hotspot IDs to ripples. */ - private SparseArray<Ripple> mTouchedRipples; + private SparseArray<Ripple> mRipples; /** Lazily-created array of actively animating ripples. */ - private Ripple[] mActiveRipples; - private int mActiveRipplesCount = 0; - - /** Lazily-created runnable for scheduling invalidation. */ - private Runnable mAnimationRunnable; + private Ripple[] mAnimatingRipples; + private int mAnimatingRipplesCount = 0; /** Paint used to control appearance of ripples. */ private Paint mRipplePaint; @@ -86,9 +79,6 @@ public class TouchFeedbackDrawable extends LayerDrawable { /** Target density of the display into which ripples are drawn. */ private float mDensity = 1.0f; - /** Whether the animation runnable has been posted. */ - private boolean mAnimating; - /** Whether bounds are being overridden. */ private boolean mOverrideBounds; @@ -154,28 +144,19 @@ public class TouchFeedbackDrawable extends LayerDrawable { private void onHotspotBoundsChange() { final int x = mHotspotBounds.centerX(); final int y = mHotspotBounds.centerY(); - final int N = mActiveRipplesCount; + final int N = mAnimatingRipplesCount; for (int i = 0; i < N; i++) { if (mState.mPinned) { - mActiveRipples[i].move(x, y); + mAnimatingRipples[i].move(x, y); } - mActiveRipples[i].onBoundsChanged(); + mAnimatingRipples[i].onBoundsChanged(); } } @Override public boolean setVisible(boolean visible, boolean restart) { if (!visible) { - if (mTouchedRipples != null) { - mTouchedRipples.clear(); - } - - if (mActiveRipplesCount > 0) { - Arrays.fill(mActiveRipples, null); - mActiveRipplesCount = 0; - mAnimating = false; - unscheduleSelf(mAnimationRunnable); - } + clearHotspots(); } return super.setVisible(visible, restart); @@ -348,21 +329,18 @@ public class TouchFeedbackDrawable extends LayerDrawable { @Override public void setHotspot(int id, float x, float y) { - if (mTouchedRipples == null) { - mTouchedRipples = new SparseArray<Ripple>(); - mActiveRipples = new Ripple[MAX_RIPPLES]; + if (mRipples == null) { + mRipples = new SparseArray<Ripple>(); + mAnimatingRipples = new Ripple[MAX_RIPPLES]; } - if (mActiveRipplesCount >= MAX_RIPPLES) { + if (mAnimatingRipplesCount >= MAX_RIPPLES) { Log.e(LOG_TAG, "Max ripple count exceeded", new RuntimeException()); return; } - final Ripple ripple = mTouchedRipples.get(id); + final Ripple ripple = mRipples.get(id); if (ripple == null) { - final Rect padding = mPaddingRect; - getPadding(padding); - final Rect bounds = mHotspotBounds; if (mState.mPinned) { x = bounds.exactCenterX(); @@ -371,11 +349,11 @@ public class TouchFeedbackDrawable extends LayerDrawable { // TODO: Clean this up in the API. final boolean pulse = (id != R.attr.state_focused); - final Ripple newRipple = new Ripple(bounds, padding, x, y, mDensity, pulse); - newRipple.animate().enter(); + final Ripple newRipple = new Ripple(this, bounds, mDensity, pulse); + newRipple.move(x, y); - mActiveRipples[mActiveRipplesCount++] = newRipple; - mTouchedRipples.put(id, newRipple); + mAnimatingRipples[mAnimatingRipplesCount++] = newRipple; + mRipples.put(id, newRipple); } else if (mState.mPinned) { final Rect bounds = mHotspotBounds; x = bounds.exactCenterX(); @@ -384,41 +362,37 @@ public class TouchFeedbackDrawable extends LayerDrawable { } else { ripple.move(x, y); } - - scheduleAnimation(); } @Override public void removeHotspot(int id) { - if (mTouchedRipples == null) { + if (mRipples == null) { return; } - final Ripple ripple = mTouchedRipples.get(id); + final Ripple ripple = mRipples.get(id); if (ripple != null) { - ripple.animate().exit(); + ripple.exit(); - mTouchedRipples.remove(id); - scheduleAnimation(); + mRipples.remove(id); } } @Override public void clearHotspots() { - if (mTouchedRipples == null) { - return; + if (mRipples != null) { + mRipples.clear(); } - final int n = mTouchedRipples.size(); - for (int i = 0; i < n; i++) { - // TODO: Use a fast exit, maybe just fade out? - mTouchedRipples.valueAt(i).animate().exit(); + final int count = mAnimatingRipplesCount; + final Ripple[] ripples = mAnimatingRipples; + for (int i = 0; i < count; i++) { + ripples[i].cancel(); + ripples[i] = null; } - if (n > 0) { - mTouchedRipples.clear(); - scheduleAnimation(); - } + mAnimatingRipplesCount = 0; + invalidateSelf(); } /** @@ -431,30 +405,6 @@ public class TouchFeedbackDrawable extends LayerDrawable { onHotspotBoundsChange(); } - /** - * Schedules the next animation, if necessary. - */ - private void scheduleAnimation() { - if (mActiveRipplesCount == 0) { - mAnimating = false; - } else if (!mAnimating) { - mAnimating = true; - - if (mAnimationRunnable == null) { - mAnimationRunnable = new Runnable() { - @Override - public void run() { - mAnimating = false; - scheduleAnimation(); - invalidateSelf(); - } - }; - } - - scheduleSelf(mAnimationRunnable, SystemClock.uptimeMillis() + 1000 / 60); - } - } - @Override public void draw(Canvas canvas) { final int N = mLayerState.mNum; @@ -501,12 +451,12 @@ public class TouchFeedbackDrawable extends LayerDrawable { } private int drawRippleLayer(Canvas canvas, Rect bounds, boolean maskOnly) { - final int ripplesCount = mActiveRipplesCount; - if (ripplesCount == 0) { + final int count = mAnimatingRipplesCount; + if (count == 0) { return -1; } - final Ripple[] activeRipples = mActiveRipples; + final Ripple[] ripples = mAnimatingRipples; final boolean projected = isProjected(); final Rect layerBounds = projected ? getDirtyBounds() : bounds; @@ -529,17 +479,15 @@ public class TouchFeedbackDrawable extends LayerDrawable { boolean drewRipples = false; int restoreToCount = -1; - int activeRipplesCount = 0; + int animatingCount = 0; - // Draw ripples. - for (int i = 0; i < ripplesCount; i++) { - final Ripple ripple = activeRipples[i]; - final RippleAnimator animator = ripple.animate(); - animator.update(); + // Draw ripples and update the animating ripples array. + for (int i = 0; i < count; i++) { + final Ripple ripple = ripples[i]; - // Mark and skip inactive ripples. - if (!animator.isRunning()) { - activeRipples[i] = null; + // Mark and skip finished ripples. + if (ripple.isFinished()) { + ripples[i] = null; continue; } @@ -565,11 +513,11 @@ public class TouchFeedbackDrawable extends LayerDrawable { drewRipples |= ripple.draw(canvas, ripplePaint); - activeRipples[activeRipplesCount] = activeRipples[i]; - activeRipplesCount++; + ripples[animatingCount] = ripples[i]; + animatingCount++; } - mActiveRipplesCount = activeRipplesCount; + mAnimatingRipplesCount = animatingCount; // If we created a layer with no content, merge it immediately. if (restoreToCount >= 0 && !drewRipples) { @@ -596,8 +544,8 @@ public class TouchFeedbackDrawable extends LayerDrawable { drawingBounds.setEmpty(); final Rect rippleBounds = mTempRect; - final Ripple[] activeRipples = mActiveRipples; - final int N = mActiveRipplesCount; + final Ripple[] activeRipples = mAnimatingRipples; + final int N = mAnimatingRipplesCount; for (int i = 0; i < N; i++) { activeRipples[i].getBounds(rippleBounds); drawingBounds.union(rippleBounds); diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 0992717..2da8615 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -14,8 +14,6 @@ package android.graphics.drawable; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; @@ -31,9 +29,6 @@ import android.graphics.Region; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; import com.android.internal.R; @@ -46,18 +41,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; /** * This lets you create a drawable based on an XML vector graphic It can be * defined in an XML file with the <code><vector></code> element. * <p/> - * The vector drawable has 6 elements: + * The vector drawable has the following elements: * <p/> * <dl> * <dt><code><vector></code></dt> - * <dd>The attribute <code>android:trigger</code> defines a state change that - * will drive the animation</dd> * <dd>The attribute <code>android:versionCode</code> defines the version of * VectorDrawable</dd> * <dt><code><size></code></dt> @@ -67,16 +59,15 @@ import java.util.HashSet; * <dd>Used to defined the size of the virtual canvas the paths are drawn on. * The size is defined using the attributes <code>android:viewportHeight</code> * <code>android:viewportWidth</code></dd> - * <dt><code><group></code></dt> - * <dd>Defines a "key frame" in the animation if there is only one group the - * drawable is static 2D image that has no animation.</dd> * <dt><code><path></code></dt> - * <dd>Defines paths to be drawn. The path elements must be within a group + * <dd>Defines paths to be drawn. Multiple paths can be defined in one xml file. + * The paths are drawn in the order of their definition order. * <dl> * <dt><code>android:name</code> * <dd>Defines the name of the path.</dd></dt> * <dt><code>android:pathData</code> - * <dd>Defines path string.</dd></dt> + * <dd>Defines path string. This is using exactly same format as "d" attribute + * in the SVG's path data</dd></dt> * <dt><code>android:fill</code> * <dd>Defines the color to fill the path (none if not present).</dd></dt> * <dt><code>android:stroke</code> @@ -109,48 +100,6 @@ import java.util.HashSet; * <dd>Sets the lineJoin for a stroked path: miter,round,bevel</dd></dt> * <dt><code>android:strokeMiterLimit</code> * <dd>Sets the Miter limit for a stroked path</dd></dt> - * <dt><code>android:state_pressed</code> - * <dd>Sets a condition to be met to draw path</dd></dt> - * <dt><code>android:state_focused</code> - * <dd>Sets a condition to be met to draw path</dd></dt> - * <dt><code>android:state_selected</code> - * <dd>Sets a condition to be met to draw path</dd></dt> - * <dt><code>android:state_window_focused</code> - * <dd>Sets a condition to be met to draw path</dd></dt> - * <dt><code>android:state_enabled</code> - * <dd>Sets a condition to be met to draw path</dd></dt> - * <dt><code>android:state_activated</code> - * <dd>Sets a condition to be met to draw path</dd></dt> - * <dt><code>android:state_accelerated</code> - * <dd>Sets a condition to be met to draw path</dd></dt> - * <dt><code>android:state_hovered</code> - * <dd>Sets a condition to be met to draw path</dd></dt> - * <dt><code>android:state_checked</code> - * <dd>Sets a condition to be met to draw path</dd></dt> - * <dt><code>android:state_checkable</code> - * <dd>Sets a condition to be met to draw path</dd></dt> - * </dl> - * </dd> - * <dt><code><animation></code></dt> - * <dd>Used to customize the transition between two paths - * <dl> - * <dt><code>android:sequence</code> - * <dd>Configures this animation sequence between the named paths.</dd></dt> - * <dt><code>android:limitTo</code> - * <dd>Limits an animation to only interpolate the selected variable unlimited, - * path, rotation, trimPathStart, trimPathEnd, trimPathOffset</dd></dt> - * <dt><code>android:repeatCount</code> - * <dd>Number of times to loop this aspect of the animation</dd></dt> - * <dt><code>android:durations</code> - * <dd>The duration of each step in the animation in milliseconds Must contain - * the number of named paths - 1</dd></dt> - * <dt><code>android:startDelay</code> - * <dd></dd></dt> - * <dt><code>android:repeatStyle</code> - * <dd>when repeating how does it repeat back and forth or a to b: forward, - * inAndOut</dd></dt> - * <dt><code>android:animate</code> - * <dd>linear, accelerate, decelerate, easing</dd></dt> * </dl> * </dd> */ @@ -159,10 +108,7 @@ public class VectorDrawable extends Drawable { private static final String SHAPE_SIZE = "size"; private static final String SHAPE_VIEWPORT = "viewport"; - private static final String SHAPE_GROUP = "group"; private static final String SHAPE_PATH = "path"; - private static final String SHAPE_TRANSITION = "transition"; - private static final String SHAPE_ANIMATION = "animation"; private static final String SHAPE_VECTOR = "vector"; private static final int LINECAP_BUTT = 0; @@ -173,37 +119,20 @@ public class VectorDrawable extends Drawable { private static final int LINEJOIN_ROUND = 1; private static final int LINEJOIN_BEVEL = 2; - private static final int DEFAULT_DURATION = 1000; - private static final long DEFAULT_INFINITE_DURATION = 60 * 60 * 1000; - private final VectorDrawableState mVectorState; private int mAlpha = 0xFF; public VectorDrawable() { mVectorState = new VectorDrawableState(null); - mVectorState.mBasicAnimator = ObjectAnimator.ofFloat(this, "AnimationFraction", 0, 0); - - setDuration(DEFAULT_DURATION); } private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) { mVectorState = new VectorDrawableState(state); - mVectorState.mBasicAnimator = ObjectAnimator.ofFloat(this, "AnimationFraction", 0, 0); if (theme != null && canApplyTheme()) { applyTheme(theme); } - - long duration = mVectorState.mVAnimatedPath.getTotalAnimationDuration(); - if (duration == -1) { - // If duration is infinite, set to 1 hour. - // TODO: Define correct approach for infinite. - duration = DEFAULT_INFINITE_DURATION; - mVectorState.mBasicAnimator.setFloatValues(0, duration / 1000); - mVectorState.mBasicAnimator.setInterpolator(new LinearInterpolator()); - } - setDuration(duration); } @Override @@ -212,123 +141,11 @@ public class VectorDrawable extends Drawable { } @Override - public void jumpToCurrentState() { - stop(); - } - - /** - * Starts the animation. - */ - public void start() { - mVectorState.mBasicAnimator.start(); - } - - /** - * Stops the animation and moves to the end state. - */ - public void stop() { - mVectorState.mBasicAnimator.end(); - } - - /** - * Returns the current completion value for the animation. - * - * @return the current point on the animation, typically between 0 and 1 - */ - public float geAnimationFraction() { - return mVectorState.mVAnimatedPath.getValue(); - } - - /** - * Set the current completion value for the animation. - * - * @param value the point along the animation, typically between 0 and 1 - */ - public void setAnimationFraction(float value) { - mVectorState.mVAnimatedPath.setAnimationFraction(value); - invalidateSelf(); - } - - /** - * set the amount of time the animation will take - * - * @param duration amount of time in milliseconds - */ - public void setDuration(long duration) { - mVectorState.mBasicAnimator.setDuration(duration); - } - - /** - * Defines what this animation should do when it reaches the end. This - * setting is applied only when the repeat count is either greater than 0 or - * {@link ValueAnimator#INFINITE}. - * - * @param mode the animation mode, either {@link ValueAnimator#RESTART} or - * {@link ValueAnimator#REVERSE} - */ - public void setRepeatMode(int mode) { - mVectorState.mBasicAnimator.setRepeatMode(mode); - } - - /** - * Sets animation to repeat - * - * @param repeat True if this drawable repeats its animation - */ - public void setRepeatCount(int repeat) { - mVectorState.mBasicAnimator.setRepeatCount(repeat); - } - - /** - * @return the animation repeat count, either a value greater than 0 or - * {@link ValueAnimator#INFINITE} - */ - public int getRepeatCount() { - return mVectorState.mBasicAnimator.getRepeatCount(); - } - - @Override - public boolean isStateful() { - return true; - } - - @Override - protected boolean onStateChange(int[] state) { - super.onStateChange(state); - - mVectorState.mVAnimatedPath.setState(state); - - final int direction = mVectorState.mVAnimatedPath.getTrigger(state); - if (direction > 0) { - animateForward(); - } else if (direction < 0) { - animateBackward(); - } - - invalidateSelf(); - return true; - } - - private void animateForward() { - if (!mVectorState.mBasicAnimator.isStarted()) { - mVectorState.mBasicAnimator.setFloatValues(0, 1); - start(); - } - } - - private void animateBackward() { - if (!mVectorState.mBasicAnimator.isStarted()) { - mVectorState.mBasicAnimator.setFloatValues(1, 0); - start(); - } - } - - @Override public void draw(Canvas canvas) { final int saveCount = canvas.save(); final Rect bounds = getBounds(); canvas.translate(bounds.left, bounds.top); - mVectorState.mVAnimatedPath.draw(canvas, bounds.width(), bounds.height()); + mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height()); canvas.restoreToCount(saveCount); } @@ -381,12 +198,12 @@ public class VectorDrawable extends Drawable { @Override public int getIntrinsicWidth() { - return (int) mVectorState.mVAnimatedPath.mBaseWidth; + return (int) mVectorState.mVPathRenderer.mBaseWidth; } @Override public int getIntrinsicHeight() { - return (int) mVectorState.mVAnimatedPath.mBaseHeight; + return (int) mVectorState.mVPathRenderer.mBaseHeight; } @Override @@ -402,8 +219,8 @@ public class VectorDrawable extends Drawable { @Override public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - final VAnimatedPath p = inflateInternal(res, parser, attrs, theme); - setAnimatedPath(p); + final VPathRenderer p = inflateInternal(res, parser, attrs, theme); + setPathRenderer(p); } @Override @@ -416,7 +233,7 @@ public class VectorDrawable extends Drawable { super.applyTheme(t); final VectorDrawableState state = mVectorState; - final VAnimatedPath path = state.mVAnimatedPath; + final VPathRenderer path = state.mVPathRenderer; if (path != null && path.canApplyTheme()) { path.applyTheme(t); } @@ -432,7 +249,6 @@ public class VectorDrawable extends Drawable { final VectorDrawable drawable = new VectorDrawable(); drawable.inflate(resources, xpp, attrs); - drawable.setAnimationFraction(0); return drawable; } catch (XmlPullParserException e) { @@ -443,16 +259,15 @@ public class VectorDrawable extends Drawable { return null; } - private VAnimatedPath inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, + private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - final VAnimatedPath animatedPath = new VAnimatedPath(); + final VPathRenderer pathRenderer = new VPathRenderer(); boolean noSizeTag = true; boolean noViewportTag = true; - boolean noGroupTag = true; boolean noPathTag = true; - VGroup currentGroup = null; + VGroup currentGroup = new VGroup(); int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { @@ -463,23 +278,14 @@ public class VectorDrawable extends Drawable { path.inflate(res, attrs, theme); currentGroup.add(path); noPathTag = false; - } else if (SHAPE_ANIMATION.equals(tagName)) { - final VAnimation anim = new VAnimation(); - anim.inflate(animatedPath.mGroupList, res, attrs, theme); - animatedPath.addAnimation(anim); } else if (SHAPE_SIZE.equals(tagName)) { - animatedPath.parseSize(res, attrs); + pathRenderer.parseSize(res, attrs); noSizeTag = false; } else if (SHAPE_VIEWPORT.equals(tagName)) { - animatedPath.parseViewport(res, attrs); + pathRenderer.parseViewport(res, attrs); noViewportTag = false; - } else if (SHAPE_GROUP.equals(tagName)) { - currentGroup = new VGroup(); - animatedPath.mGroupList.add(currentGroup); - noGroupTag = false; } else if (SHAPE_VECTOR.equals(tagName)) { final TypedArray a = res.obtainAttributes(attrs, R.styleable.VectorDrawable); - animatedPath.setTrigger(a.getInteger(R.styleable.VectorDrawable_trigger, 0)); // Parsing the version information. // Right now, we only support version "1". @@ -498,7 +304,7 @@ public class VectorDrawable extends Drawable { eventType = parser.next(); } - if (noSizeTag || noViewportTag || noGroupTag || noPathTag) { + if (noSizeTag || noViewportTag || noPathTag) { final StringBuffer tag = new StringBuffer(); if (noSizeTag) { @@ -512,13 +318,6 @@ public class VectorDrawable extends Drawable { tag.append(SHAPE_SIZE); } - if (noGroupTag) { - if (tag.length() > 0) { - tag.append(" & "); - } - tag.append(SHAPE_GROUP); - } - if (noPathTag) { if (tag.length() > 0) { tag.append(" or "); @@ -529,49 +328,25 @@ public class VectorDrawable extends Drawable { throw new XmlPullParserException("no " + tag + " defined"); } + pathRenderer.mCurrentGroup = currentGroup; // post parse cleanup - animatedPath.parseFinish(); - return animatedPath; - } - - private void setAnimatedPath(VAnimatedPath animatedPath) { - mVectorState.mVAnimatedPath = animatedPath; - - long duration = mVectorState.mVAnimatedPath.getTotalAnimationDuration(); - if (duration == -1) { // if it set to infinite set to 1 hour - duration = DEFAULT_INFINITE_DURATION; // TODO define correct - // approach for infinite - mVectorState.mBasicAnimator.setFloatValues(0, duration / 1000); - mVectorState.mBasicAnimator.setInterpolator(new LinearInterpolator()); - } - - setDuration(duration); - setAnimationFraction(0); + pathRenderer.parseFinish(); + return pathRenderer; } - @Override - public boolean setVisible(boolean visible, boolean restart) { - boolean changed = super.setVisible(visible, restart); - if (visible) { - if (changed || restart) { - setAnimationFraction(0); - } - } else { - stop(); - } - return changed; + private void setPathRenderer(VPathRenderer pathRenderer) { + mVectorState.mVPathRenderer = pathRenderer; } private static class VectorDrawableState extends ConstantState { int mChangingConfigurations; - ValueAnimator mBasicAnimator; - VAnimatedPath mVAnimatedPath; + VPathRenderer mVPathRenderer; Rect mPadding; public VectorDrawableState(VectorDrawableState copy) { if (copy != null) { mChangingConfigurations = copy.mChangingConfigurations; - mVAnimatedPath = new VAnimatedPath(copy.mVAnimatedPath); + mVPathRenderer = new VPathRenderer(copy.mVPathRenderer); mPadding = new Rect(copy.mPadding); } } @@ -597,164 +372,60 @@ public class VectorDrawable extends Drawable { } } - private static class VAnimatedPath { - private static final int [] TRIGGER_MAP = { - 0, - R.attr.state_pressed, - R.attr.state_focused, - R.attr.state_hovered, - R.attr.state_selected, - R.attr.state_checkable, - R.attr.state_checked, - R.attr.state_activated, - R.attr.state_focused - }; - + private static class VPathRenderer { private final Path mPath = new Path(); private final Path mRenderPath = new Path(); private final Matrix mMatrix = new Matrix(); - private ArrayList<VAnimation> mCurrentAnimList; private VPath[] mCurrentPaths; private Paint mStrokePaint; private Paint mFillPaint; private PathMeasure mPathMeasure; - private int[] mCurrentState = new int[0]; - private float mAnimationValue; - private long mTotalDuration; - private int mTrigger; - private boolean mTriggerState; - - final ArrayList<VGroup> mGroupList = new ArrayList<VGroup>(); + private VGroup mCurrentGroup = new VGroup(); float mBaseWidth = 1; float mBaseHeight = 1; float mViewportWidth; float mViewportHeight; - public VAnimatedPath() { + public VPathRenderer() { } - public VAnimatedPath(VAnimatedPath copy) { - mCurrentAnimList = new ArrayList<VAnimation>(copy.mCurrentAnimList); - mGroupList.addAll(copy.mGroupList); + public VPathRenderer(VPathRenderer copy) { + mCurrentGroup = copy.mCurrentGroup; if (copy.mCurrentPaths != null) { mCurrentPaths = new VPath[copy.mCurrentPaths.length]; for (int i = 0; i < mCurrentPaths.length; i++) { mCurrentPaths[i] = new VPath(copy.mCurrentPaths[i]); } } - mAnimationValue = copy.mAnimationValue; // time goes from 0 to 1 mBaseWidth = copy.mBaseWidth; mBaseHeight = copy.mBaseHeight; mViewportWidth = copy.mViewportHeight; mViewportHeight = copy.mViewportHeight; - mTotalDuration = copy.mTotalDuration; - mTrigger = copy.mTrigger; - mCurrentState = new int[0]; } public boolean canApplyTheme() { - final ArrayList<VGroup> groups = mGroupList; - for (int i = groups.size() - 1; i >= 0; i--) { - final ArrayList<VPath> paths = groups.get(i).mVGList; - for (int j = paths.size() - 1; j >= 0; j--) { - final VPath path = paths.get(j); - if (path.canApplyTheme()) { - return true; - } - } - } - - final ArrayList<VAnimation> anims = mCurrentAnimList; - for (int i = anims.size() - 1; i >= 0; i--) { - final VAnimation anim = anims.get(i); - if (anim.canApplyTheme()) { + final ArrayList<VPath> paths = mCurrentGroup.mVGList; + for (int j = paths.size() - 1; j >= 0; j--) { + final VPath path = paths.get(j); + if (path.canApplyTheme()) { return true; } } - return false; } public void applyTheme(Theme t) { - final ArrayList<VGroup> groups = mGroupList; - for (int i = groups.size() - 1; i >= 0; i--) { - final ArrayList<VPath> paths = groups.get(i).mVGList; - for (int j = paths.size() - 1; j >= 0; j--) { - final VPath path = paths.get(j); - if (path.canApplyTheme()) { - path.applyTheme(t); - } + final ArrayList<VPath> paths = mCurrentGroup.mVGList; + for (int j = paths.size() - 1; j >= 0; j--) { + final VPath path = paths.get(j); + if (path.canApplyTheme()) { + path.applyTheme(t); } } - - final ArrayList<VAnimation> anims = mCurrentAnimList; - for (int i = anims.size() - 1; i >= 0; i--) { - final VAnimation anim = anims.get(i); - if (anim.canApplyTheme()) { - anim.applyTheme(t); - } - } - } - - public void setTrigger(int trigger){ - mTrigger = VAnimatedPath.getStateForTrigger(trigger); - } - - public long getTotalAnimationDuration() { - mTotalDuration = 0; - int size = mCurrentAnimList.size(); - for (int i = 0; i < size; i++) { - VAnimation vAnimation = mCurrentAnimList.get(i); - long t = vAnimation.getTotalDuration(); - if (t == -1) { - mTotalDuration = -1; - return -1; - } - mTotalDuration = Math.max(mTotalDuration, t); - } - - return mTotalDuration; - } - - public float getValue() { - return mAnimationValue; - } - - /** - * @param value the point along the animations to show typically between 0.0f and 1.0f - * @return true if you need to keep repeating - */ - public boolean setAnimationFraction(float value) { - getTotalAnimationDuration(); - - long animationTime = (long) (value * mTotalDuration); - - final int len = mCurrentPaths.length; - for (int i = 0; i < len; i++) { - animationTime = - (long) ((mTotalDuration == -1) ? value * 1000 : mTotalDuration * value); - - final VPath path = mCurrentPaths[i]; - final int size = mCurrentAnimList.size(); - for (int j = 0; j < size; j++) { - final VAnimation vAnimation = mCurrentAnimList.get(j); - if (vAnimation.doesAdjustPath(path)) { - mCurrentPaths[i] = vAnimation.getPathAtTime(animationTime, path); - } - } - } - - mAnimationValue = value; - - if (mTotalDuration == -1) { - return true; - } else { - return animationTime < mTotalDuration; - } } public void draw(Canvas canvas, int w, int h) { @@ -764,7 +435,7 @@ public class VectorDrawable extends Drawable { } for (int i = 0; i < mCurrentPaths.length; i++) { - if (mCurrentPaths[i] != null && mCurrentPaths[i].isVisible(mCurrentState)) { + if (mCurrentPaths[i] != null) { drawPath(mCurrentPaths[i], canvas, w, h); } } @@ -846,69 +517,17 @@ public class VectorDrawable extends Drawable { } /** - * Ensure there is at least one animation for every path in group (linking them by names) - * Build the "current" path based on the first group + * Build the "current" path based on the current group * TODO: improve memory use & performance or move to C++ */ public void parseFinish() { - final HashMap<String, VAnimation> newAnimations = new HashMap<String, VAnimation>(); - for (VGroup group : mGroupList) { - for (VPath vPath : group.getPaths()) { - if (!vPath.mAnimated) { - VAnimation ap = null; - - if (!newAnimations.containsKey(vPath.getID())) { - newAnimations.put(vPath.getID(), ap = new VAnimation()); - } else { - ap = newAnimations.get(vPath.getID()); - } - - ap.addPath(vPath); - vPath.mAnimated = true; - } - } - } - - if (mCurrentAnimList == null) { - mCurrentAnimList = new ArrayList<VectorDrawable.VAnimation>(); - } - mCurrentAnimList.addAll(newAnimations.values()); - - final Collection<VPath> paths = mGroupList.get(0).getPaths(); + final Collection<VPath> paths = mCurrentGroup.getPaths(); mCurrentPaths = paths.toArray(new VPath[paths.size()]); for (int i = 0; i < mCurrentPaths.length; i++) { mCurrentPaths[i] = new VPath(mCurrentPaths[i]); } } - public void setState(int[] state) { - mCurrentState = Arrays.copyOf(state, state.length); - } - - int getTrigger(int []state){ - if (mTrigger == 0) return 0; - for (int i = 0; i < state.length; i++) { - if (state[i] == mTrigger){ - if (mTriggerState) - return 0; - mTriggerState = true; - return 1; - } - } - if (mTriggerState) { - mTriggerState = false; - return -1; - } - return 0; - } - - public void addAnimation(VAnimation anim) { - if (mCurrentAnimList == null) { - mCurrentAnimList = new ArrayList<VectorDrawable.VAnimation>(); - } - mCurrentAnimList.add(anim); - } - private void parseViewport(Resources r, AttributeSet attrs) throws XmlPullParserException { final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableViewport); @@ -933,329 +552,6 @@ public class VectorDrawable extends Drawable { a.recycle(); } - private static final int getStateForTrigger(int trigger) { - return TRIGGER_MAP[trigger]; - } - } - - private static class VAnimation { - private static final String SEPARATOR = ","; - - private static final int DIRECTION_FORWARD = 0; - private static final int DIRECTION_IN_AND_OUT = 1; - - public enum Style { - INTERPOLATE, CROSSFADE, WIPE - } - - private final HashSet<String> mSeqMap = new HashSet<String>(); - - private Interpolator mAnimInterpolator = new AccelerateDecelerateInterpolator(); - private VPath[] mPaths = new VPath[0]; - private long[] mDuration = { DEFAULT_DURATION }; - - private int[] mThemeAttrs; - private Style mStyle; - private int mLimitProperty = 0; - private long mStartOffset; - private long mRepeat = 1; - private long mWipeDirection; - private int mMode = DIRECTION_FORWARD; - private int mInterpolatorType; - private String mId; - - public VAnimation() { - // Empty constructor. - } - - public void inflate(ArrayList<VGroup> groups, Resources r, AttributeSet attrs, Theme theme) - throws XmlPullParserException { - String value; - String[] sp; - int name; - - final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableAnimation); - final int[] themeAttrs = a.extractThemeAttrs(); - mThemeAttrs = themeAttrs; - - value = a.getString(R.styleable.VectorDrawableAnimation_sequence); - if (value != null) { - sp = value.split(SEPARATOR); - final VectorDrawable.VPath[] paths = new VectorDrawable.VPath[sp.length]; - - for (int j = 0; j < sp.length; j++) { - mSeqMap.add(sp[j].trim()); - - final VectorDrawable.VPath path = groups.get(j).get(sp[j]); - if (path == null) { - throw new XmlPullParserException(a.getPositionDescription() - + " missing path with name: " + sp[j]); - } - - path.mAnimated = true; - paths[j] = path; - } - - setPaths(paths); - } - - name = R.styleable.VectorDrawableAnimation_durations; - value = a.getString(name); - if (value != null) { - long totalDuration = 0; - sp = value.split(SEPARATOR); - - final long[] dur = new long[sp.length]; - for (int j = 0; j < dur.length; j++) { - dur[j] = Long.parseLong(sp[j]); - totalDuration += dur[j]; - } - - if (totalDuration == 0){ - throw new XmlPullParserException(a.getPositionDescription() - + " total duration must not be zero"); - } - - setDuration(dur); - } - - setLimitProperty(a.getInt(R.styleable.VectorDrawableAnimation_limitTo, 0)); - setRepeat(a.getInt(R.styleable.VectorDrawableAnimation_repeatCount, 1)); - setStartOffset(a.getInt(R.styleable.VectorDrawableAnimation_startDelay, 0)); - setMode(a.getInt(R.styleable.VectorDrawableAnimation_repeatStyle, 0)); - - fixMissingParameters(); - - a.recycle(); - } - - public boolean canApplyTheme() { - return mThemeAttrs != null; - } - - public void applyTheme(Theme t) { - // TODO: Apply theme. - } - - public boolean doesAdjustPath(VPath path) { - return mSeqMap.contains(path.getID()); - } - - public String getId() { - if (mId == null) { - mId = mPaths[0].getID(); - for (int i = 1; i < mPaths.length; i++) { - mId += mPaths[i].getID(); - } - } - return mId; - } - - public String getPathName() { - return mPaths[0].getID(); - } - - public Style getStyle() { - return mStyle; - } - - public void setStyle(Style style) { - mStyle = style; - } - - public int getLimitProperty() { - return mLimitProperty; - } - - public void setLimitProperty(int limitProperty) { - mLimitProperty = limitProperty; - } - - public long[] getDuration() { - return mDuration; - } - - public void setDuration(long[] duration) { - mDuration = duration; - } - - public long getRepeat() { - return mRepeat; - } - - public void setRepeat(long repeat) { - mRepeat = repeat; - } - - public long getStartOffset() { - return mStartOffset; - } - - public void setStartOffset(long startOffset) { - mStartOffset = startOffset; - } - - public long getWipeDirection() { - return mWipeDirection; - } - - public void setWipeDirection(long wipeDirection) { - mWipeDirection = wipeDirection; - } - - public int getMode() { - return mMode; - } - - public void setMode(int mode) { - mMode = mode; - } - - public int getInterpolator() { - return mInterpolatorType; - } - - public void setInterpolator(int interpolator) { - mInterpolatorType = interpolator; - } - - /** - * compute the total time in milliseconds - * - * @return the total time in milliseconds the animation will take - */ - public long getTotalDuration() { - long total = mStartOffset; - if (getRepeat() == -1) { - return -1; - } - for (int i = 0; i < mDuration.length; i++) { - if (mRepeat > 1) { - total += mDuration[i] * mRepeat; - } else { - total += mDuration[i]; - } - } - return total; - } - - public void setPaths(VPath[] paths) { - mPaths = paths; - } - - public void addPath(VPath path) { - mPaths = Arrays.copyOf(mPaths, mPaths.length + 1); - mPaths[mPaths.length - 1] = path; - } - - public boolean containsPath(String pathid) { - for (int i = 0; i < mPaths.length; i++) { - if (mPaths[i].getID().equals(pathid)) { - return true; - } - } - return false; - } - - public void interpolate(VPath p1, VPath p2, float time, VPath dest) { - VPath.interpolate(time, p1, p2, dest, mLimitProperty); - } - - public VPath getPathAtTime(long milliseconds, VPath dest) { - if (mPaths.length == 1) { - dest.copyFrom(mPaths[0]); - return dest; - } - long point = milliseconds - mStartOffset; - if (point < 0) { - point = 0; - } - float time = 0; - long sum = mDuration[0]; - for (int i = 1; i < mDuration.length; i++) { - sum += mDuration[i]; - } - - if (mRepeat > 1) { - time = point / (float) (sum * mRepeat); - time = mAnimInterpolator.getInterpolation(time); - - if (mMode == DIRECTION_IN_AND_OUT) { - point = ((long) (time * sum * 2 * mRepeat)) % (sum * 2); - if (point > sum) { - point = sum * 2 - point; - } - } else { - point = ((long) (time * sum * mRepeat)) % sum; - } - } else if (mRepeat == 1) { - time = point / (float) (sum * mRepeat); - time = mAnimInterpolator.getInterpolation(time); - if (mMode == DIRECTION_IN_AND_OUT) { - point = ((long) (time * sum * 2 * mRepeat)); - if (point > sum) { - point = sum * 2 - point; - } - } else { - point = Math.min(((long) (time * sum * mRepeat)), sum); - } - - } else { // repeat = -1 - if (mMode == DIRECTION_IN_AND_OUT) { - point = point % (sum * 2); - if (point > sum) { - point = sum * 2 - point; - } - time = point / (float) sum; - } else { - point = point % sum; - time = point / (float) sum; - } - } - - int transition = 0; - while (point > mDuration[transition]) { - point -= mDuration[transition++]; - } - if (mPaths.length > (transition + 1)) { - if (mPaths[transition].getID() != dest.getID()) { - dest.copyFrom(mPaths[transition]); - } - interpolate(mPaths[transition], mPaths[transition + 1], - point / (float) mDuration[transition], dest); - } else { - interpolate(mPaths[transition], mPaths[transition], 0, dest); - } - return dest; - } - - void fixMissingParameters() { - // fix missing points - float rotation = Float.NaN; - float rotationY = Float.NaN; - float rotationX = Float.NaN; - for (int i = 0; i < mPaths.length; i++) { - if (mPaths[i].mPivotX > 0) { - rotationX = mPaths[i].mPivotX; - } - if (mPaths[i].mPivotY > 0) { - rotationY = mPaths[i].mPivotY; - } - if (mPaths[i].mRotate > 0) { - rotation = mPaths[i].mRotate; - } - } - if (rotation > 0) { - for (int i = 0; i < mPaths.length; i++) { - if (mPaths[i].mPivotX == 0) { - mPaths[i].mPivotX = rotationX; - } - if (mPaths[i].mPivotY == 0) { - mPaths[i].mPivotY = rotationY; - } - } - } - } } private static class VGroup { @@ -1268,10 +564,6 @@ public class VectorDrawable extends Drawable { mVGList.add(path); } - public VPath get(String name) { - return mVGPathMap.get(name); - } - /** * Must return in order of adding * @return ordered list of paths @@ -1280,23 +572,9 @@ public class VectorDrawable extends Drawable { return mVGList; } - public int size() { - return mVGPathMap.size(); - } } private static class VPath { - private static final int LIMIT_ALL = 0; - private static final int LIMIT_PATH = 1; - private static final int LIMIT_ROTATE = 2; - private static final int LIMIT_TRIM_PATH_START = 3; - private static final int LIMIT_TRIM_PATH_OFFSET = 5; - private static final int LIMIT_TRIM_PATH_END = 4; - - private static final int STATE_UNDEFINED=0; - private static final int STATE_TRUE=1; - private static final int STATE_FALSE=2; - private static final int MAX_STATES = 10; private int[] mThemeAttrs; @@ -1317,7 +595,6 @@ public class VectorDrawable extends Drawable { float mTrimPathEnd = 1; float mTrimPathOffset = 0; - boolean mAnimated = false; boolean mClip = false; Paint.Cap mStrokeLineCap = Paint.Cap.BUTT; Paint.Join mStrokeLineJoin = Paint.Join.MITER; @@ -1328,7 +605,6 @@ public class VectorDrawable extends Drawable { private int[] mCheckState = new int[MAX_STATES]; private boolean[] mCheckValue = new boolean[MAX_STATES]; private int mNumberOfStates = 0; - private int mNumberOfTrue = 0; public VPath() { // Empty constructor. @@ -1338,38 +614,6 @@ public class VectorDrawable extends Drawable { copyFrom(p); } - public void addStateFilter(int state, boolean condition) { - int k = 0; - while (k < mNumberOfStates) { - if (mCheckState[mNumberOfStates] == state) - break; - k++; - } - mCheckState[k] = state; - mCheckValue[k] = condition; - if (k==mNumberOfStates){ - mNumberOfStates++; - } - if (condition) { - mNumberOfTrue++; - } - } - - private int getState(int state){ - for (int i = 0; i < mNumberOfStates; i++) { - if (mCheckState[mNumberOfStates] == state){ - return (mCheckValue[i])?STATE_TRUE:STATE_FALSE; - } - } - return STATE_UNDEFINED; - } - /** - * @return the name of the path - */ - public String getName() { - return mId; - } - public void toPath(Path path) { path.reset(); if (mNode != null) { @@ -1494,27 +738,6 @@ public class VectorDrawable extends Drawable { R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart); } - // TODO: Consider replacing this with existing state attributes. - final int[] states = { - R.styleable.VectorDrawablePath_state_activated, - R.styleable.VectorDrawablePath_state_checkable, - R.styleable.VectorDrawablePath_state_checked, - R.styleable.VectorDrawablePath_state_enabled, - R.styleable.VectorDrawablePath_state_focused, - R.styleable.VectorDrawablePath_state_hovered, - R.styleable.VectorDrawablePath_state_pressed, - R.styleable.VectorDrawablePath_state_selected, - R.styleable.VectorDrawablePath_state_window_focused - }; - - final int N = states.length; - for (int i = 0; i < N; i++) { - final int state = states[i]; - if (a.hasValue(state)) { - addStateFilter(state, a.getBoolean(state, false)); - } - } - updateColorAlphas(); a.recycle(); @@ -1682,7 +905,6 @@ public class VectorDrawable extends Drawable { mRotate = p1.mRotate; mPivotX = p1.mPivotX; mPivotY = p1.mPivotY; - mAnimated = p1.mAnimated; mTrimPathStart = p1.mTrimPathStart; mTrimPathEnd = p1.mTrimPathEnd; mTrimPathOffset = p1.mTrimPathOffset; @@ -1697,118 +919,6 @@ public class VectorDrawable extends Drawable { mFillRule = p1.mFillRule; } - - public static VPath interpolate(float t, VPath p1, VPath p2, VPath returnPath, int limit) { - if (limit == LIMIT_ALL || limit == LIMIT_PATH) { - if (returnPath.mNode == null || returnPath.mNode.length != p1.mNode.length) { - returnPath.mNode = new VNode[p1.mNode.length]; - } - for (int i = 0; i < returnPath.mNode.length; i++) { - if (returnPath.mNode[i] == null) { - returnPath.mNode[i] = new VNode(p1.mNode[i], p2.mNode[i], t); - } else { - returnPath.mNode[i].interpolate(p1.mNode[i], p2.mNode[i], t); - } - } - } - float t1 = 1 - t; - switch (limit) { - case LIMIT_ALL: - returnPath.mRotate = t1 * p1.mRotate + t * p2.mRotate; - returnPath.mPivotX = t1 * p1.mPivotX + t * p2.mPivotX; - returnPath.mPivotY = t1 * p1.mPivotY + t * p2.mPivotY; - returnPath.mClip = p1.mClip | p2.mClip; - - returnPath.mTrimPathStart = t1 * p1.mTrimPathStart + t * p2.mTrimPathStart; - returnPath.mTrimPathEnd = t1 * p1.mTrimPathEnd + t * p2.mTrimPathEnd; - returnPath.mTrimPathOffset = t1 * p1.mTrimPathOffset + t * p2.mTrimPathOffset; - returnPath.mStrokeMiterlimit = - t1 * p1.mStrokeMiterlimit + t * p2.mStrokeMiterlimit; - returnPath.mStrokeLineCap = p1.mStrokeLineCap; - if (returnPath.mStrokeLineCap == null) { - returnPath.mStrokeLineCap = p2.mStrokeLineCap; - } - returnPath.mStrokeLineJoin = p1.mStrokeLineJoin; - if (returnPath.mStrokeLineJoin == null) { - returnPath.mStrokeLineJoin = p2.mStrokeLineJoin; - } - returnPath.mFillRule = p1.mFillRule; - - returnPath.mStrokeColor = rgbInterpolate(t, p1.mStrokeColor, p2.mStrokeColor); - returnPath.mFillColor = rgbInterpolate(t, p1.mFillColor, p2.mFillColor); - returnPath.mStrokeWidth = t1 * p1.mStrokeWidth + t * p2.mStrokeWidth; - returnPath.mNumberOfStates = p1.mNumberOfStates; - for (int i = 0; i < returnPath.mNumberOfStates; i++) { - returnPath.mCheckState[i] = p1.mCheckState[i]; - returnPath.mCheckValue[i] = p1.mCheckValue[i]; - } - for (int i = 0; i < p2.mNumberOfStates; i++) { - returnPath.addStateFilter(p2.mCheckState[i], p2.mCheckValue[i]); - } - - int count = 0; - for (int i = 0; i < returnPath.mNumberOfStates; i++) { - if (returnPath.mCheckValue[i]) { - count++; - } - } - returnPath.mNumberOfTrue = count; - break; - case LIMIT_ROTATE: - returnPath.mRotate = t1 * p1.mRotate + t * p2.mRotate; - break; - case LIMIT_TRIM_PATH_END: - returnPath.mTrimPathEnd = t1 * p1.mTrimPathEnd + t * p2.mTrimPathEnd; - break; - case LIMIT_TRIM_PATH_OFFSET: - returnPath.mTrimPathOffset = t1 * p1.mTrimPathOffset + t * p2.mTrimPathOffset; - break; - case LIMIT_TRIM_PATH_START: - returnPath.mTrimPathStart = t1 * p1.mTrimPathStart + t * p2.mTrimPathStart; - break; - } - return returnPath; - } - - private static int rgbInterpolate(float fraction, int startColor, int endColor) { - if (startColor == endColor) { - return startColor; - } else if (startColor == 0) { - return endColor; - } else if (endColor == 0) { - return startColor; - } - - final int startA = (startColor >> 24) & 0xff; - final int startR = (startColor >> 16) & 0xff; - final int startG = (startColor >> 8) & 0xff; - final int startB = startColor & 0xff; - - final int endA = (endColor >> 24) & 0xff; - final int endR = (endColor >> 16) & 0xff; - final int endG = (endColor >> 8) & 0xff; - final int endB = endColor & 0xff; - - return ((startA + (int)(fraction * (endA - startA))) << 24) | - ((startR + (int)(fraction * (endR - startR))) << 16) | - ((startG + (int)(fraction * (endG - startG))) << 8) | - ((startB + (int)(fraction * (endB - startB)))); - } - - public boolean isVisible(int[] state) { - int match = 0; - for (int i = 0; i < state.length; i++) { - int v = getState(state[i]); - if (v != STATE_UNDEFINED) { - if (v==STATE_TRUE) { - match++; - } else { - return false; - } - } - } - return match == mNumberOfTrue; - } } private static class VNode { @@ -1825,25 +935,6 @@ public class VectorDrawable extends Drawable { mParams = Arrays.copyOf(n.mParams, n.mParams.length); } - public VNode(VNode n1, VNode n2, float t) { - mType = n1.mType; - mParams = new float[n1.mParams.length]; - interpolate(n1, n2, t); - } - - private boolean match(VNode n) { - if (n.mType != mType) { - return false; - } - return (mParams.length == n.mParams.length); - } - - public void interpolate(VNode n1, VNode n2, float t) { - for (int i = 0; i < n1.mParams.length; i++) { - mParams[i] = n1.mParams[i] * (1 - t) + n2.mParams[i] * t; - } - } - public static void createPath(VNode[] node, Path path) { float[] current = new float[4]; char previousCommand = 'm'; @@ -1899,7 +990,6 @@ public class VectorDrawable extends Drawable { break; } for (int k = 0; k < val.length; k += incr) { - // TODO: build test to prove all permutations work switch (cmd) { case 'm': // moveto - Start a new sub-path (relative) path.rMoveTo(val[k + 0], val[k + 1]); |