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:
Chet Haase
2013-03-21 00:05:34 +00:00
committed by Android Git Automerger
5 changed files with 294 additions and 10 deletions

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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>();

View 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) {}
}

View File

@@ -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