am 75dfe46e: Merge "Add auto-cancel ability to ObjectAnimator" into jb-mr2-dev
* commit '75dfe46e36ccb8b7b000f44c5b78c82bde478fff': Add auto-cancel ability to ObjectAnimator
This commit is contained in:
@@ -2445,6 +2445,7 @@ package android.animation {
|
||||
method public static android.animation.ObjectAnimator ofObject(java.lang.Object, java.lang.String, android.animation.TypeEvaluator, java.lang.Object...);
|
||||
method public static android.animation.ObjectAnimator ofObject(T, android.util.Property<T, V>, android.animation.TypeEvaluator<V>, V...);
|
||||
method public static android.animation.ObjectAnimator ofPropertyValuesHolder(java.lang.Object, android.animation.PropertyValuesHolder...);
|
||||
method public void setAutoCancel(boolean);
|
||||
method public void setProperty(android.util.Property);
|
||||
method public void setPropertyName(java.lang.String);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package android.animation;
|
||||
import android.util.Log;
|
||||
import android.util.Property;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
@@ -49,6 +48,8 @@ public final class ObjectAnimator extends ValueAnimator {
|
||||
|
||||
private Property mProperty;
|
||||
|
||||
private boolean mAutoCancel = false;
|
||||
|
||||
/**
|
||||
* Sets the name of the property that will be animated. This name is used to derive
|
||||
* a setter function that will be called to set animated values.
|
||||
@@ -346,17 +347,83 @@ public final class ObjectAnimator extends ValueAnimator {
|
||||
// No values yet - this animator is being constructed piecemeal. Init the values with
|
||||
// whatever the current propertyName is
|
||||
if (mProperty != null) {
|
||||
setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator)null, values));
|
||||
setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator) null, values));
|
||||
} else {
|
||||
setValues(PropertyValuesHolder.ofObject(mPropertyName, (TypeEvaluator)null, values));
|
||||
setValues(PropertyValuesHolder.ofObject(mPropertyName,
|
||||
(TypeEvaluator) null, values));
|
||||
}
|
||||
} else {
|
||||
super.setObjectValues(values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* autoCancel controls whether an ObjectAnimator will be canceled automatically
|
||||
* when any other ObjectAnimator with the same target and properties is started.
|
||||
* Setting this flag may make it easier to run different animators on the same target
|
||||
* object without having to keep track of whether there are conflicting animators that
|
||||
* need to be manually canceled. Canceling animators must have the same exact set of
|
||||
* target properties, in the same order.
|
||||
*
|
||||
* @param cancel Whether future ObjectAnimators with the same target and properties
|
||||
* as this ObjectAnimator will cause this ObjectAnimator to be canceled.
|
||||
*/
|
||||
public void setAutoCancel(boolean cancel) {
|
||||
mAutoCancel = cancel;
|
||||
}
|
||||
|
||||
private boolean hasSameTargetAndProperties(Animator anim) {
|
||||
if (anim instanceof ObjectAnimator) {
|
||||
PropertyValuesHolder[] theirValues = ((ObjectAnimator) anim).getValues();
|
||||
if (((ObjectAnimator) anim).getTarget() == mTarget &&
|
||||
mValues.length == theirValues.length) {
|
||||
for (int i = 0; i < mValues.length; ++i) {
|
||||
PropertyValuesHolder pvhMine = mValues[i];
|
||||
PropertyValuesHolder pvhTheirs = theirValues[i];
|
||||
if (pvhMine.getPropertyName() == null ||
|
||||
!pvhMine.getPropertyName().equals(pvhTheirs.getPropertyName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
// See if any of the current active/pending animators need to be canceled
|
||||
AnimationHandler handler = sAnimationHandler.get();
|
||||
if (handler != null) {
|
||||
int numAnims = handler.mAnimations.size();
|
||||
for (int i = numAnims - 1; i >= 0; i--) {
|
||||
if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
|
||||
ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
|
||||
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
|
||||
anim.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
numAnims = handler.mPendingAnimations.size();
|
||||
for (int i = numAnims - 1; i >= 0; i--) {
|
||||
if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
|
||||
ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
|
||||
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
|
||||
anim.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
numAnims = handler.mDelayedAnims.size();
|
||||
for (int i = numAnims - 1; i >= 0; i--) {
|
||||
if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
|
||||
ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
|
||||
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
|
||||
anim.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DBG) {
|
||||
Log.d("ObjectAnimator", "Anim target, duration: " + mTarget + ", " + getDuration());
|
||||
for (int i = 0; i < mValues.length; ++i) {
|
||||
|
||||
@@ -83,7 +83,10 @@ public class ValueAnimator extends Animator {
|
||||
|
||||
// The static sAnimationHandler processes the internal timing loop on which all animations
|
||||
// are based
|
||||
private static ThreadLocal<AnimationHandler> sAnimationHandler =
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
protected static ThreadLocal<AnimationHandler> sAnimationHandler =
|
||||
new ThreadLocal<AnimationHandler>();
|
||||
|
||||
// The time interpolator to be used if none is set on the animation
|
||||
@@ -531,22 +534,27 @@ public class ValueAnimator extends Animator {
|
||||
* animations possible.
|
||||
*
|
||||
* The handler uses the Choreographer for executing periodic callbacks.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
private static class AnimationHandler implements Runnable {
|
||||
protected static class AnimationHandler implements Runnable {
|
||||
// The per-thread list of all active animations
|
||||
private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
|
||||
/** @hide */
|
||||
protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
|
||||
|
||||
// Used in doAnimationFrame() to avoid concurrent modifications of mAnimations
|
||||
private final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>();
|
||||
|
||||
// The per-thread set of animations to be started on the next animation frame
|
||||
private final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();
|
||||
/** @hide */
|
||||
protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();
|
||||
|
||||
/**
|
||||
* Internal per-thread collections used to avoid set collisions as animations start and end
|
||||
* while being processed.
|
||||
* @hide
|
||||
*/
|
||||
private final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
|
||||
protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
|
||||
private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
|
||||
private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();
|
||||
|
||||
|
||||
201
core/tests/coretests/src/android/animation/AutoCancelTest.java
Normal file
201
core/tests/coretests/src/android/animation/AutoCancelTest.java
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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.animation;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AutoCancelTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
|
||||
|
||||
boolean mAnimX1Canceled = false;
|
||||
boolean mAnimXY1Canceled = false;
|
||||
boolean mAnimX2Canceled = false;
|
||||
boolean mAnimXY2Canceled = false;
|
||||
|
||||
private static final long START_DELAY = 100;
|
||||
private static final long DELAYED_START_DURATION = 200;
|
||||
private static final long FUTURE_TIMEOUT = 1000;
|
||||
|
||||
HashMap<Animator, Boolean> mCanceledMap = new HashMap<Animator, Boolean>();
|
||||
|
||||
public AutoCancelTest() {
|
||||
super(BasicAnimatorActivity.class);
|
||||
}
|
||||
|
||||
ObjectAnimator setupAnimator(long startDelay, String... properties) {
|
||||
ObjectAnimator returnVal;
|
||||
if (properties.length == 1) {
|
||||
returnVal = ObjectAnimator.ofFloat(this, properties[0], 0, 1);
|
||||
} else {
|
||||
PropertyValuesHolder[] pvhArray = new PropertyValuesHolder[properties.length];
|
||||
for (int i = 0; i < properties.length; i++) {
|
||||
pvhArray[i] = PropertyValuesHolder.ofFloat(properties[i], 0, 1);
|
||||
}
|
||||
returnVal = ObjectAnimator.ofPropertyValuesHolder(this, pvhArray);
|
||||
}
|
||||
returnVal.setAutoCancel(true);
|
||||
returnVal.setStartDelay(startDelay);
|
||||
returnVal.addListener(mCanceledListener);
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
private void setupAnimators(long startDelay, boolean startLater, final FutureWaiter future)
|
||||
throws Exception {
|
||||
// Animators to be auto-canceled
|
||||
final ObjectAnimator animX1 = setupAnimator(startDelay, "x");
|
||||
final ObjectAnimator animY1 = setupAnimator(startDelay, "y");
|
||||
final ObjectAnimator animXY1 = setupAnimator(startDelay, "x", "y");
|
||||
final ObjectAnimator animXZ1 = setupAnimator(startDelay, "x", "z");
|
||||
|
||||
animX1.start();
|
||||
animY1.start();
|
||||
animXY1.start();
|
||||
animXZ1.start();
|
||||
|
||||
final ObjectAnimator animX2 = setupAnimator(0, "x");
|
||||
animX2.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
// We expect only animX1 to be canceled at this point
|
||||
if (mCanceledMap.get(animX1) == null ||
|
||||
mCanceledMap.get(animX1) != true ||
|
||||
mCanceledMap.get(animY1) != null ||
|
||||
mCanceledMap.get(animXY1) != null ||
|
||||
mCanceledMap.get(animXZ1) != null) {
|
||||
future.set(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final ObjectAnimator animXY2 = setupAnimator(0, "x", "y");
|
||||
animXY2.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
// We expect only animXY1 to be canceled at this point
|
||||
if (mCanceledMap.get(animXY1) == null ||
|
||||
mCanceledMap.get(animXY1) != true ||
|
||||
mCanceledMap.get(animY1) != null ||
|
||||
mCanceledMap.get(animXZ1) != null) {
|
||||
future.set(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
// Release future if not done already via failures during start
|
||||
future.release();
|
||||
}
|
||||
});
|
||||
|
||||
if (startLater) {
|
||||
Handler handler = new Handler();
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
animX2.start();
|
||||
animXY2.start();
|
||||
}
|
||||
}, DELAYED_START_DURATION);
|
||||
} else {
|
||||
animX2.start();
|
||||
animXY2.start();
|
||||
}
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testAutoCancel() throws Exception {
|
||||
final FutureWaiter future = new FutureWaiter();
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
setupAnimators(0, false, future);
|
||||
} catch (Exception e) {
|
||||
future.setException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
assertTrue(future.get(FUTURE_TIMEOUT, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testAutoCancelDelayed() throws Exception {
|
||||
final FutureWaiter future = new FutureWaiter();
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
setupAnimators(START_DELAY, false, future);
|
||||
} catch (Exception e) {
|
||||
future.setException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
assertTrue(future.get(FUTURE_TIMEOUT, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testAutoCancelTestLater() throws Exception {
|
||||
final FutureWaiter future = new FutureWaiter();
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
setupAnimators(0, true, future);
|
||||
} catch (Exception e) {
|
||||
future.setException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
assertTrue(future.get(FUTURE_TIMEOUT, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testAutoCancelDelayedTestLater() throws Exception {
|
||||
final FutureWaiter future = new FutureWaiter();
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
setupAnimators(START_DELAY, true, future);
|
||||
} catch (Exception e) {
|
||||
future.setException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
assertTrue(future.get(FUTURE_TIMEOUT, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
private AnimatorListenerAdapter mCanceledListener = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
mCanceledMap.put(animation, true);
|
||||
}
|
||||
};
|
||||
|
||||
public void setX(float x) {}
|
||||
|
||||
public void setY(float y) {}
|
||||
|
||||
public void setZ(float z) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,21 @@ import com.google.common.util.concurrent.AbstractFuture;
|
||||
* {@link com.google.common.util.concurrent.AbstractFuture#set(Object)} method internally. It
|
||||
* also exposes the protected {@link AbstractFuture#setException(Throwable)} method.
|
||||
*/
|
||||
public class FutureWaiter extends AbstractFuture<Void> {
|
||||
public class FutureWaiter extends AbstractFuture<Boolean> {
|
||||
|
||||
/**
|
||||
* Release the Future currently waiting on
|
||||
* {@link com.google.common.util.concurrent.AbstractFuture#get()}.
|
||||
*/
|
||||
public void release() {
|
||||
super.set(null);
|
||||
super.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to indicate failure (when the result value is false).
|
||||
*/
|
||||
public void set(boolean result) {
|
||||
super.set(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user