am d1477e74: Merge "Better Transition interruption" into klp-dev

* commit 'd1477e746065450b1900398e103f4715ccf81b35':
  Better Transition interruption
This commit is contained in:
Chet Haase
2013-08-16 16:46:31 -07:00
committed by Android Git Automerger
8 changed files with 567 additions and 118 deletions

View File

@ -29496,19 +29496,18 @@ package android.view.transition {
public abstract class Transition implements java.lang.Cloneable {
ctor public Transition();
method public void addListener(android.view.transition.Transition.TransitionListener);
method protected void cancelTransition();
method protected void cancel();
method protected abstract void captureValues(android.view.transition.TransitionValues, boolean);
method public android.view.transition.Transition clone();
method public long getDuration();
method public android.animation.TimeInterpolator getInterpolator();
method public java.util.ArrayList<android.view.transition.Transition.TransitionListener> getListeners();
method public java.lang.String getName();
method public long getStartDelay();
method public int[] getTargetIds();
method public android.view.View[] getTargets();
method public java.lang.String[] getTransitionProperties();
method protected android.view.transition.TransitionValues getTransitionValues(android.view.View, boolean);
method protected void onTransitionCancel();
method protected void onTransitionEnd();
method protected void onTransitionStart();
method protected android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
method public void removeListener(android.view.transition.Transition.TransitionListener);
method public android.view.transition.Transition setDuration(long);
@ -29521,6 +29520,8 @@ package android.view.transition {
public static abstract interface Transition.TransitionListener {
method public abstract void onTransitionCancel(android.view.transition.Transition);
method public abstract void onTransitionEnd(android.view.transition.Transition);
method public abstract void onTransitionPause(android.view.transition.Transition);
method public abstract void onTransitionResume(android.view.transition.Transition);
method public abstract void onTransitionStart(android.view.transition.Transition);
}
@ -29567,6 +29568,7 @@ package android.view.transition {
method protected android.animation.Animator appear(android.view.ViewGroup, android.view.transition.TransitionValues, int, android.view.transition.TransitionValues, int);
method protected void captureValues(android.view.transition.TransitionValues, boolean);
method protected android.animation.Animator disappear(android.view.ViewGroup, android.view.transition.TransitionValues, int, android.view.transition.TransitionValues, int);
method public boolean isVisible(android.view.transition.TransitionValues);
}
}

View File

@ -5356,6 +5356,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
/**
* Returns whether layout calls on this container are currently being
* suppressed, due to an earlier call to {@link #suppressLayout(boolean)}.
*
* @return true if layout calls are currently suppressed, false otherwise.
*
* @hide
*/
public boolean isLayoutSuppressed() {
return mSuppressLayout;
}
/**
* {@inheritDoc}
*/

View File

@ -19,6 +19,7 @@ package android.view.transition;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@ -35,6 +36,7 @@ public class Fade extends Visibility {
private static boolean DBG = Transition.DBG && false;
private static final String LOG_TAG = "Fade";
private static final String PROPNAME_ALPHA = "android:fade:alpha";
private static final String PROPNAME_SCREEN_X = "android:fade:screenX";
private static final String PROPNAME_SCREEN_Y = "android:fade:screenY";
@ -74,26 +76,51 @@ public class Fade extends Visibility {
/**
* Utility method to handle creating and running the Animator.
*/
private Animator runAnimation(View view, float startAlpha, float endAlpha,
Animator.AnimatorListener listener) {
private Animator createAnimation(View view, float startAlpha, float endAlpha,
AnimatorListenerAdapter listener) {
if (startAlpha == endAlpha) {
// run listener if we're noop'ing the animation, to get the end-state results now
if (listener != null) {
listener.onAnimationEnd(null);
}
return null;
}
final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", startAlpha, endAlpha);
if (listener != null) {
anim.addListener(listener);
anim.addPauseListener(listener);
}
// TODO: Maybe extract a method into Transition to run an animation that handles the
// duration/startDelay stuff for all subclasses.
return anim;
}
@Override
protected void captureValues(TransitionValues values, boolean start) {
super.captureValues(values, start);
float alpha = values.view.getAlpha();
values.values.put(PROPNAME_ALPHA, alpha);
int[] loc = new int[2];
values.view.getLocationOnScreen(loc);
values.values.put(PROPNAME_SCREEN_X, loc[0]);
values.values.put(PROPNAME_SCREEN_Y, loc[1]);
}
@Override
protected Animator play(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
Animator animator = super.play(sceneRoot, startValues, endValues);
if (animator == null && startValues != null && endValues != null) {
boolean endVisible = isVisible(endValues);
final View endView = endValues.view;
float endAlpha = endView.getAlpha();
float startAlpha = (Float) startValues.values.get(PROPNAME_ALPHA);
if ((endVisible && startAlpha < endAlpha && (mFadingMode & Fade.IN) != 0) ||
(!endVisible && startAlpha > endAlpha && (mFadingMode & Fade.OUT) != 0)) {
animator = createAnimation(endView, startAlpha, endAlpha, null);
}
}
return animator;
}
@Override
protected Animator appear(ViewGroup sceneRoot,
TransitionValues startValues, int startVisibility,
@ -102,15 +129,11 @@ public class Fade extends Visibility {
return null;
}
final View endView = endValues.view;
endView.setAlpha(0);
final Animator.AnimatorListener endListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Always end animation with full alpha, in case it's canceled mid-stream
endView.setAlpha(1);
}
};
return runAnimation(endView, 0, 1, endListener);
// if alpha < 1, just fade it in from the current value
if (endView.getAlpha() == 1.0f) {
endView.setAlpha(0);
}
return createAnimation(endView, endView.getAlpha(), 1, null);
}
@Override
@ -129,7 +152,7 @@ public class Fade extends Visibility {
}
View overlayView = null;
View viewToKeep = null;
if (endView == null) {
if (endView == null || endView.getParent() == null) {
// view was removed: add the start view to the Overlay
view = startView;
overlayView = view;
@ -167,7 +190,7 @@ public class Fade extends Visibility {
final View finalOverlayView = overlayView;
final View finalViewToKeep = viewToKeep;
final ViewGroup finalSceneRoot = sceneRoot;
final Animator.AnimatorListener endListener = new AnimatorListenerAdapter() {
final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finalView.setAlpha(startAlpha);
@ -179,8 +202,22 @@ public class Fade extends Visibility {
finalSceneRoot.getOverlay().remove(finalOverlayView);
}
}
@Override
public void onAnimationPause(Animator animation) {
if (finalOverlayView != null) {
finalSceneRoot.getOverlay().remove(finalOverlayView);
}
}
@Override
public void onAnimationResume(Animator animation) {
if (finalOverlayView != null) {
finalSceneRoot.getOverlay().add(finalOverlayView);
}
}
};
return runAnimation(view, startAlpha, endAlpha, endListener);
return createAnimation(view, startAlpha, endAlpha, endListener);
}
if (viewToKeep != null) {
// TODO: find a different way to do this, like just changing the view to be
@ -193,12 +230,42 @@ public class Fade extends Visibility {
final View finalOverlayView = overlayView;
final View finalViewToKeep = viewToKeep;
final ViewGroup finalSceneRoot = sceneRoot;
final Animator.AnimatorListener endListener = new AnimatorListenerAdapter() {
final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
boolean mCanceled = false;
float mPausedAlpha = -1;
@Override
public void onAnimationPause(Animator animation) {
if (finalViewToKeep != null && !mCanceled) {
finalViewToKeep.setVisibility(finalVisibility);
}
mPausedAlpha = finalView.getAlpha();
finalView.setAlpha(startAlpha);
}
@Override
public void onAnimationResume(Animator animation) {
if (finalViewToKeep != null && !mCanceled) {
finalViewToKeep.setVisibility(View.VISIBLE);
}
finalView.setAlpha(mPausedAlpha);
}
@Override
public void onAnimationCancel(Animator animation) {
mCanceled = true;
if (mPausedAlpha >= 0) {
finalView.setAlpha(mPausedAlpha);
}
}
@Override
public void onAnimationEnd(Animator animation) {
finalView.setAlpha(startAlpha);
if (!mCanceled) {
finalView.setAlpha(startAlpha);
}
// TODO: restore view offset from overlay repositioning
if (finalViewToKeep != null) {
if (finalViewToKeep != null && !mCanceled) {
finalViewToKeep.setVisibility(finalVisibility);
}
if (finalOverlayView != null) {
@ -206,7 +273,7 @@ public class Fade extends Visibility {
}
}
};
return runAnimation(view, startAlpha, endAlpha, endListener);
return createAnimation(view, startAlpha, endAlpha, endListener);
}
return null;
}

View File

@ -25,8 +25,6 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@ -42,6 +40,13 @@ public class Move extends Transition {
private static final String PROPNAME_PARENT = "android:move:parent";
private static final String PROPNAME_WINDOW_X = "android:move:windowX";
private static final String PROPNAME_WINDOW_Y = "android:move:windowY";
private static String[] sTransitionProperties = {
PROPNAME_BOUNDS,
PROPNAME_PARENT,
PROPNAME_WINDOW_X,
PROPNAME_WINDOW_Y
};
int[] tempLocation = new int[2];
boolean mResizeClip = false;
boolean mReparent = false;
@ -49,6 +54,11 @@ public class Move extends Transition {
private static RectEvaluator sRectEvaluator = new RectEvaluator();
@Override
public String[] getTransitionProperties() {
return sTransitionProperties;
}
public void setResizeClip(boolean resizeClip) {
mResizeClip = resizeClip;
}
@ -146,12 +156,33 @@ public class Move extends Transition {
if (view.getParent() instanceof ViewGroup) {
final ViewGroup parent = (ViewGroup) view.getParent();
parent.suppressLayout(true);
anim.addListener(new AnimatorListenerAdapter() {
TransitionListener transitionListener = new TransitionListenerAdapter() {
boolean mCanceled = false;
@Override
public void onAnimationEnd(Animator animation) {
public void onTransitionCancel(Transition transition) {
parent.suppressLayout(false);
mCanceled = true;
}
@Override
public void onTransitionEnd(Transition transition) {
if (!mCanceled) {
parent.suppressLayout(false);
}
}
@Override
public void onTransitionPause(Transition transition) {
parent.suppressLayout(false);
}
});
@Override
public void onTransitionResume(Transition transition) {
parent.suppressLayout(true);
}
};
addListener(transitionListener);
}
return anim;
} else {
@ -191,12 +222,33 @@ public class Move extends Transition {
if (view.getParent() instanceof ViewGroup) {
final ViewGroup parent = (ViewGroup) view.getParent();
parent.suppressLayout(true);
anim.addListener(new AnimatorListenerAdapter() {
TransitionListener transitionListener = new TransitionListenerAdapter() {
boolean mCanceled = false;
@Override
public void onAnimationEnd(Animator animation) {
public void onTransitionCancel(Transition transition) {
parent.suppressLayout(false);
mCanceled = true;
}
@Override
public void onTransitionEnd(Transition transition) {
if (!mCanceled) {
parent.suppressLayout(false);
}
}
@Override
public void onTransitionPause(Transition transition) {
parent.suppressLayout(false);
}
});
@Override
public void onTransitionResume(Transition transition) {
parent.suppressLayout(true);
}
};
addListener(transitionListener);
}
anim.addListener(new AnimatorListenerAdapter() {
@Override

View File

@ -22,7 +22,6 @@ import android.animation.TimeInterpolator;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
import android.util.SparseArray;
import android.view.SurfaceView;
import android.view.TextureView;
@ -60,6 +59,8 @@ public abstract class Transition implements Cloneable {
private static final String LOG_TAG = "Transition";
static final boolean DBG = false;
private String mName = getClass().getName();
long mStartDelay = -1;
long mDuration = -1;
TimeInterpolator mInterpolator = null;
@ -69,29 +70,29 @@ public abstract class Transition implements Cloneable {
private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
TransitionGroup mParent = null;
// Per-animator information used for later canceling when future transitions overlap
private static ThreadLocal<ArrayMap<Animator, AnimationInfo>> sRunningAnimators =
new ThreadLocal<ArrayMap<Animator, AnimationInfo>>();
// Scene Root is set at play() time in the cloned Transition
ViewGroup mSceneRoot = null;
// Used to carry data between setup() and play(), cleared before every scene transition
private ArrayList<TransitionValues> mPlayStartValuesList = new ArrayList<TransitionValues>();
private ArrayList<TransitionValues> mPlayEndValuesList = new ArrayList<TransitionValues>();
// Track all animators in use in case the transition gets canceled and needs to
// cancel running animators
private ArrayList<Animator> mCurrentAnimators = new ArrayList<Animator>();
// Number of per-target instances of this Transition currently running. This count is
// determined by calls to startTransition() and endTransition()
// determined by calls to start() and end()
int mNumInstances = 0;
// Whether this transition is currently paused, due to a call to pause()
boolean mPaused = false;
// The set of listeners to be sent transition lifecycle events.
ArrayList<TransitionListener> mListeners = null;
// The set of animators collected from calls to play(), to be run in runAnimations()
ArrayMap<Pair<TransitionValues, TransitionValues>, Animator> mAnimatorMap =
new ArrayMap<Pair<TransitionValues, TransitionValues>, Animator>();
ArrayList<Animator> mAnimators = new ArrayList<Animator>();
/**
* Constructs a Transition object with no target objects. A transition with
@ -115,6 +116,14 @@ public abstract class Transition implements Cloneable {
return this;
}
/**
* Returns the duration set on this transition. If no duration has been set,
* the returned value will be negative, indicating that resulting animators will
* retain their own durations.
*
* @return The duration set on this transition, if one has been set, otherwise
* returns a negative number.
*/
public long getDuration() {
return mDuration;
}
@ -131,6 +140,14 @@ public abstract class Transition implements Cloneable {
mStartDelay = startDelay;
}
/**
* Returns the startDelay set on this transition. If no startDelay has been set,
* the returned value will be negative, indicating that resulting animators will
* retain their own startDelays.
*
* @return The startDealy set on this transition, if one has been set, otherwise
* returns a negative number.
*/
public long getStartDelay() {
return mStartDelay;
}
@ -147,10 +164,43 @@ public abstract class Transition implements Cloneable {
mInterpolator = interpolator;
}
/**
* Returns the interpolator set on this transition. If no interpolator has been set,
* the returned value will be null, indicating that resulting animators will
* retain their own interpolators.
*
* @return The interpolator set on this transition, if one has been set, otherwise
* returns null.
*/
public TimeInterpolator getInterpolator() {
return mInterpolator;
}
/**
* Returns the set of property names used stored in the {@link TransitionValues}
* object passed into {@link #captureValues(TransitionValues, boolean)} that
* this transition cares about for the purposes of canceling overlapping animations.
* When any transition is started on a given scene root, all transitions
* currently running on that same scene root are checked to see whether the
* properties on which they based their animations agree with the end values of
* the same properties in the new transition. If the end values are not equal,
* then the old animation is canceled since the new transition will start a new
* animation to these new values. If the values are equal, the old animation is
* allowed to continue and no new animation is started for that transition.
*
* <p>A transition does not need to override this method. However, not doing so
* will mean that the cancellation logic outlined in the previous paragraph
* will be skipped for that transition, possibly leading to artifacts as
* old transitions and new transitions on the same targets run in parallel,
* animating views toward potentially different end values.</p>
*
* @return An array of property names as described in the class documentation for
* {@link TransitionValues}. The default implementation returns <code>null</code>.
*/
public String[] getTransitionProperties() {
return null;
}
/**
* This method is called by the transition's parent (all the way up to the
* topmost Transition in the hierarchy) with the sceneRoot and start/end
@ -210,8 +260,6 @@ public abstract class Transition implements Cloneable {
if (DBG) {
Log.d(LOG_TAG, "play() for " + this);
}
mPlayStartValuesList.clear();
mPlayEndValuesList.clear();
ArrayMap<View, TransitionValues> endCopy =
new ArrayMap<View, TransitionValues>(endValues.viewValues);
SparseArray<TransitionValues> endIdCopy =
@ -316,6 +364,7 @@ public abstract class Transition implements Cloneable {
startValuesList.add(start);
endValuesList.add(end);
}
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
for (int i = 0; i < startValuesList.size(); ++i) {
TransitionValues start = startValuesList.get(i);
TransitionValues end = endValuesList.get(i);
@ -345,14 +394,46 @@ public abstract class Transition implements Cloneable {
// TODO: what to do about targetIds and itemIds?
Animator animator = play(sceneRoot, start, end);
if (animator != null) {
mAnimatorMap.put(new Pair(start, end), animator);
// Note: we've already done the check against targetIDs in these lists
mPlayStartValuesList.add(start);
mPlayEndValuesList.add(end);
// Save animation info for future cancellation purposes
View view = null;
TransitionValues infoValues = null;
if (end != null) {
view = end.view;
String[] properties = getTransitionProperties();
if (view != null && properties != null && properties.length > 0) {
infoValues = new TransitionValues();
infoValues.view = view;
TransitionValues newValues = endValues.viewValues.get(view);
if (newValues != null) {
for (int j = 0; j < properties.length; ++j) {
infoValues.values.put(properties[j],
newValues.values.get(properties[j]));
}
}
int numExistingAnims = runningAnimators.size();
for (int j = 0; j < numExistingAnims; ++j) {
Animator anim = runningAnimators.keyAt(j);
AnimationInfo info = runningAnimators.get(anim);
if (info.values != null && info.view == view &&
((info.name == null && getName() == null) ||
info.name.equals(getName()))) {
if (info.values.equals(infoValues)) {
// Favor the old animator
animator = null;
break;
}
}
}
}
} else {
view = (start != null) ? start.view : null;
}
if (animator != null) {
AnimationInfo info = new AnimationInfo(view, getName(), infoValues);
runningAnimators.put(animator, info);
mAnimators.add(animator);
}
}
} else if (DBG) {
View view = (end != null) ? end.view : start.view;
Log.d(LOG_TAG, " No change for view " + view);
}
}
}
@ -389,6 +470,15 @@ public abstract class Transition implements Cloneable {
return false;
}
private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() {
ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get();
if (runningAnimators == null) {
runningAnimators = new ArrayMap<Animator, AnimationInfo>();
sRunningAnimators.set(runningAnimators);
}
return runningAnimators;
}
/**
* This is called internally once all animations have been set up by the
* transition hierarchy. \
@ -396,28 +486,27 @@ public abstract class Transition implements Cloneable {
* @hide
*/
protected void runAnimations() {
if (DBG && mPlayStartValuesList.size() > 0) {
Log.d(LOG_TAG, "runAnimations (" + mPlayStartValuesList.size() + ") on " + this);
if (DBG) {
Log.d(LOG_TAG, "runAnimations() on " + this);
}
startTransition();
// Now walk the list of TransitionValues, calling play for each pair
for (int i = 0; i < mPlayStartValuesList.size(); ++i) {
TransitionValues start = mPlayStartValuesList.get(i);
TransitionValues end = mPlayEndValuesList.get(i);
Animator anim = mAnimatorMap.get(new Pair(start, end));
start();
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
// Now start every Animator that was previously created for this transition in play()
for (Animator anim : mAnimators) {
if (DBG) {
Log.d(LOG_TAG, " anim: " + anim);
}
startTransition();
runAnimator(anim);
if (runningAnimators.containsKey(anim)) {
start();
runAnimator(anim, runningAnimators);
}
}
mPlayStartValuesList.clear();
mPlayEndValuesList.clear();
mAnimatorMap.clear();
endTransition();
mAnimators.clear();
end();
}
private void runAnimator(Animator animator) {
private void runAnimator(Animator animator,
final ArrayMap<Animator, AnimationInfo> runningAnimators) {
if (animator != null) {
// TODO: could be a single listener instance for all of them since it uses the param
animator.addListener(new AnimatorListenerAdapter() {
@ -427,6 +516,7 @@ public abstract class Transition implements Cloneable {
}
@Override
public void onAnimationEnd(Animator animation) {
runningAnimators.remove(animation);
mCurrentAnimators.remove(animation);
}
});
@ -690,12 +780,113 @@ public abstract class Transition implements Cloneable {
return values;
}
/**
* Pauses this transition, sending out calls to {@link
* TransitionListener#onTransitionPause(Transition)} to all listeners
* and pausing all running animators started by this transition.
*
* @hide
*/
public void pause() {
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
int numOldAnims = runningAnimators.size();
for (int i = numOldAnims - 1; i >= 0; i--) {
Animator anim = runningAnimators.keyAt(i);
anim.pause();
}
if (mListeners != null && mListeners.size() > 0) {
ArrayList<TransitionListener> tmpListeners =
(ArrayList<TransitionListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onTransitionPause(this);
}
}
mPaused = true;
}
/**
* Resumes this transition, sending out calls to {@link
* TransitionListener#onTransitionPause(Transition)} to all listeners
* and pausing all running animators started by this transition.
*
* @hide
*/
public void resume() {
if (mPaused) {
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
int numOldAnims = runningAnimators.size();
for (int i = numOldAnims - 1; i >= 0; i--) {
Animator anim = runningAnimators.keyAt(i);
anim.resume();
}
if (mListeners != null && mListeners.size() > 0) {
ArrayList<TransitionListener> tmpListeners =
(ArrayList<TransitionListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onTransitionResume(this);
}
}
mPaused = false;
}
}
/**
* Called by TransitionManager to play the transition. This calls
* play() to set things up and create all of the animations and then
* runAnimations() to actually start the animations.
*/
void playTransition(ViewGroup sceneRoot) {
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
int numOldAnims = runningAnimators.size();
for (int i = numOldAnims - 1; i >= 0; i--) {
Animator anim = runningAnimators.keyAt(i);
if (anim != null) {
anim.resume();
AnimationInfo oldInfo = runningAnimators.get(anim);
if (oldInfo != null) {
boolean cancel = false;
TransitionValues oldValues = oldInfo.values;
View oldView = oldInfo.view;
TransitionValues newValues = mEndValues.viewValues != null ?
mEndValues.viewValues.get(oldView) : null;
if (oldValues == null || newValues == null) {
if (oldValues != null || newValues != null) {
cancel = true;
}
} else {
for (String key : oldValues.values.keySet()) {
Object oldValue = oldValues.values.get(key);
Object newValue = newValues.values.get(key);
if ((oldValue == null && newValue != null) ||
(oldValue != null && !oldValue.equals(newValue))) {
cancel = true;
if (DBG) {
Log.d(LOG_TAG, "Transition.play: oldValue != newValue for " +
key + ": old, new = " + oldValue + ", " + newValue);
}
break;
}
}
}
if (cancel) {
if (anim.isRunning() || anim.isStarted()) {
if (DBG) {
Log.d(LOG_TAG, "Canceling anim " + anim);
}
anim.cancel();
} else {
if (DBG) {
Log.d(LOG_TAG, "removing anim from info list: " + anim);
}
runningAnimators.remove(anim);
}
}
}
}
}
// setup() must be called on entire transition hierarchy and set of views
// before calling play() on anything; every transition needs a chance to set up
// target views appropriately before transitions begin running
@ -707,7 +898,7 @@ public abstract class Transition implements Cloneable {
* This is a utility method used by subclasses to handle standard parts of
* setting up and running an Animator: it sets the {@link #getDuration()
* duration} and the {@link #getStartDelay() startDelay}, starts the
* animation, and, when the animator ends, calls {@link #endTransition()}.
* animation, and, when the animator ends, calls {@link #end()}.
*
* @param animator The Animator to be run during this transition.
*
@ -716,7 +907,7 @@ public abstract class Transition implements Cloneable {
protected void animate(Animator animator) {
// TODO: maybe pass auto-end as a boolean parameter?
if (animator == null) {
endTransition();
end();
} else {
if (getDuration() >= 0) {
animator.setDuration(getDuration());
@ -730,7 +921,7 @@ public abstract class Transition implements Cloneable {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
endTransition();
end();
animation.removeListener(this);
}
});
@ -738,30 +929,6 @@ public abstract class Transition implements Cloneable {
}
}
/**
* Subclasses may override to receive notice of when the transition starts.
* This is equivalent to listening for the
* {@link TransitionListener#onTransitionStart(Transition)} callback.
*/
protected void onTransitionStart() {
}
/**
* Subclasses may override to receive notice of when the transition is
* canceled. This is equivalent to listening for the
* {@link TransitionListener#onTransitionCancel(Transition)} callback.
*/
protected void onTransitionCancel() {
}
/**
* Subclasses may override to receive notice of when the transition ends.
* This is equivalent to listening for the
* {@link TransitionListener#onTransitionEnd(Transition)} callback.
*/
protected void onTransitionEnd() {
}
/**
* This method is called automatically by the transition and
* TransitionGroup classes prior to a Transition subclass starting;
@ -769,9 +936,8 @@ public abstract class Transition implements Cloneable {
*
* @hide
*/
protected void startTransition() {
protected void start() {
if (mNumInstances == 0) {
onTransitionStart();
if (mListeners != null && mListeners.size() > 0) {
ArrayList<TransitionListener> tmpListeners =
(ArrayList<TransitionListener>) mListeners.clone();
@ -790,15 +956,14 @@ public abstract class Transition implements Cloneable {
* a transition did nothing (returned a null Animator from
* {@link Transition#play(ViewGroup, TransitionValues,
* TransitionValues)}) or because the transition returned a valid
* Animator and endTransition() was called in the onAnimationEnd()
* Animator and end() was called in the onAnimationEnd()
* callback of the AnimatorListener.
*
* @hide
*/
protected void endTransition() {
protected void end() {
--mNumInstances;
if (mNumInstances == 0) {
onTransitionEnd();
if (mListeners != null && mListeners.size() > 0) {
ArrayList<TransitionListener> tmpListeners =
(ArrayList<TransitionListener>) mListeners.clone();
@ -828,7 +993,7 @@ public abstract class Transition implements Cloneable {
* This method cancels a transition that is currently running.
* Implementation TBD.
*/
protected void cancelTransition() {
protected void cancel() {
// TODO: how does this work with instances?
// TODO: this doesn't actually do *anything* yet
int numAnimators = mCurrentAnimators.size();
@ -836,7 +1001,6 @@ public abstract class Transition implements Cloneable {
Animator animator = mCurrentAnimators.get(i);
animator.cancel();
}
onTransitionCancel();
if (mListeners != null && mListeners.size() > 0) {
ArrayList<TransitionListener> tmpListeners =
(ArrayList<TransitionListener>) mListeners.clone();
@ -901,11 +1065,28 @@ public abstract class Transition implements Cloneable {
Transition clone = null;
try {
clone = (Transition) super.clone();
clone.mAnimators = new ArrayList<Animator>();
} catch (CloneNotSupportedException e) {}
return clone;
}
/**
* Returns the name of this Transition. This name is used internally to distinguish
* between different transitions to determine when interrupting transitions overlap.
* For example, a Move running on the same target view as another Move should determine
* whether the old transition is animating to different end values and should be
* canceled in favor of the new transition.
*
* <p>By default, a Transition's name is simply the value of {@link Class#getName()},
* but subclasses are free to override and return something different.</p>
*
* @return The name of this transition.
*/
public String getName() {
return mName;
}
String toString(String indent) {
String result = indent + getClass().getSimpleName() + "@" +
Integer.toHexString(hashCode()) + ": ";
@ -943,8 +1124,7 @@ public abstract class Transition implements Cloneable {
/**
* A transition listener receives notifications from a transition.
* Notifications indicate transition lifecycle events: when the transition
* begins, ends, or is canceled.
* Notifications indicate transition lifecycle events.
*/
public static interface TransitionListener {
/**
@ -957,7 +1137,7 @@ public abstract class Transition implements Cloneable {
/**
* Notification about the end of the transition. Canceled transitions
* will always notify listeners of both the cancellation and end
* events. That is, {@link #onTransitionEnd()} is always called,
* events. That is, {@link #onTransitionEnd(Transition)} is always called,
* regardless of whether the transition was canceled or played
* through to completion.
*
@ -967,10 +1147,38 @@ public abstract class Transition implements Cloneable {
/**
* Notification about the cancellation of the transition.
* Note that cancel() may be called by a parent {@link TransitionGroup} on
* a child transition which has not yet started. This allows the child
* transition to restore state on target objects which was set at
* {@link #play(android.view.ViewGroup, TransitionValues, TransitionValues)
* play()} time.
*
* @param transition The transition which was canceled.
*/
void onTransitionCancel(Transition transition);
/**
* Notification when a transition is paused.
* Note that play() may be called by a parent {@link TransitionGroup} on
* a child transition which has not yet started. This allows the child
* transition to restore state on target objects which was set at
* {@link #play(android.view.ViewGroup, TransitionValues, TransitionValues)
* play()} time.
*
* @param transition The transition which was paused.
*/
void onTransitionPause(Transition transition);
/**
* Notification when a transition is resumed.
* Note that resume() may be called by a parent {@link TransitionGroup} on
* a child transition which has not yet started. This allows the child
* transition to restore state which may have changed in an earlier call
* to {@link #onTransitionPause(Transition)}.
*
* @param transition The transition which was resumed.
*/
void onTransitionResume(Transition transition);
}
/**
@ -991,6 +1199,32 @@ public abstract class Transition implements Cloneable {
@Override
public void onTransitionCancel(Transition transition) {
}
@Override
public void onTransitionPause(Transition transition) {
}
@Override
public void onTransitionResume(Transition transition) {
}
}
/**
* Holds information about each animator used when a new transition starts
* while other transitions are still running to determine whether a running
* animation should be canceled or a new animation noop'd. The structure holds
* information about the state that an animation is going to, to be compared to
* end state of a new animation.
*/
private static class AnimationInfo {
View view;
String name;
TransitionValues values;
AnimationInfo(View view, String name, TransitionValues values) {
this.view = view;
this.name = name;
this.values = values;
}
}
}

View File

@ -164,7 +164,7 @@ public class TransitionGroup extends Transition {
@Override
public void onTransitionStart(Transition transition) {
if (!mTransitionGroup.mStarted) {
mTransitionGroup.startTransition();
mTransitionGroup.start();
mTransitionGroup.mStarted = true;
}
}
@ -175,7 +175,7 @@ public class TransitionGroup extends Transition {
if (mTransitionGroup.mCurrentListeners == 0) {
// All child trans
mTransitionGroup.mStarted = false;
mTransitionGroup.endTransition();
mTransitionGroup.end();
}
transition.removeListener(this);
}
@ -233,12 +233,32 @@ public class TransitionGroup extends Transition {
}
}
/** @hide */
@Override
protected void cancelTransition() {
super.cancelTransition();
public void pause() {
super.pause();
int numTransitions = mTransitions.size();
for (int i = 0; i < numTransitions; ++i) {
mTransitions.get(i).cancelTransition();
mTransitions.get(i).pause();
}
}
/** @hide */
@Override
public void resume() {
super.resume();
int numTransitions = mTransitions.size();
for (int i = 0; i < numTransitions; ++i) {
mTransitions.get(i).resume();
}
}
@Override
protected void cancel() {
super.cancel();
int numTransitions = mTransitions.size();
for (int i = 0; i < numTransitions; ++i) {
mTransitions.get(i).cancel();
}
}

View File

@ -18,6 +18,7 @@ package android.view.transition;
import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@ -45,8 +46,8 @@ public class TransitionManager {
ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
static ArrayMap<ViewGroup, Transition> sRunningTransitions =
new ArrayMap<ViewGroup, Transition>();
private static ThreadLocal<ArrayMap<ViewGroup, ArrayList<Transition>>> sRunningTransitions =
new ThreadLocal<ArrayMap<ViewGroup, ArrayList<Transition>>>();
private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>();
@ -160,6 +161,16 @@ public class TransitionManager {
sceneChangeRunTransition(sceneRoot, transitionClone);
}
private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() {
ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
sRunningTransitions.get();
if (runningTransitions == null) {
runningTransitions = new ArrayMap<ViewGroup, ArrayList<Transition>>();
sRunningTransitions.set(runningTransitions);
}
return runningTransitions;
}
private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
final Transition transition) {
if (transition != null) {
@ -169,16 +180,31 @@ public class TransitionManager {
sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
sPendingTransitions.remove(sceneRoot);
// Add to running list, handle end to remove it
sRunningTransitions.put(sceneRoot, transition);
final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
getRunningTransitions();
ArrayList<Transition> currentTransitions = runningTransitions.get(sceneRoot);
if (currentTransitions == null) {
currentTransitions = new ArrayList<Transition>();
runningTransitions.put(sceneRoot, currentTransitions);
}
currentTransitions.add(transition);
transition.addListener(new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
sRunningTransitions.remove(sceneRoot);
ArrayList<Transition> currentTransitions =
runningTransitions.get(sceneRoot);
currentTransitions.remove(transition);
}
});
transition.captureValues(sceneRoot, false);
transition.playTransition(sceneRoot);
return true;
// Returning false from onPreDraw() skips the current frame. This is
// necessary to avoid artifacts caused by resetting target views
// to their proper end states for capturing. Waiting until the next
// frame to draw allows these views to have their mid-transition
// values set on them again and avoid artifacts.
return false;
}
});
}
@ -187,16 +213,18 @@ public class TransitionManager {
private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
// Capture current values
Transition runningTransition = sRunningTransitions.get(sceneRoot);
ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
if (runningTransitions != null && runningTransitions.size() > 0) {
for (Transition runningTransition : runningTransitions) {
runningTransition.pause();
}
}
if (transition != null) {
transition.captureValues(sceneRoot, true);
}
if (runningTransition != null) {
runningTransition.cancelTransition();
}
// Notify previous scene that it is being exited
Scene previousScene = sceneRoot.getCurrentScene();
if (previousScene != null) {

View File

@ -19,6 +19,7 @@ package android.view.transition;
import android.animation.Animator;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOverlay;
import android.view.ViewParent;
/**
@ -38,6 +39,10 @@ public abstract class Visibility extends Transition {
private static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
private static final String PROPNAME_PARENT = "android:visibility:parent";
private static String[] sTransitionProperties = {
PROPNAME_VISIBILITY,
PROPNAME_PARENT,
};
private static class VisibilityInfo {
boolean visibilityChange;
@ -51,6 +56,11 @@ public abstract class Visibility extends Transition {
// Temporary structure, used in calculating state in setup() and play()
private VisibilityInfo mTmpVisibilityInfo = new VisibilityInfo();
@Override
public String[] getTransitionProperties() {
return sTransitionProperties;
}
@Override
protected void captureValues(TransitionValues values, boolean start) {
int visibility = values.view.getVisibility();
@ -58,6 +68,31 @@ public abstract class Visibility extends Transition {
values.values.put(PROPNAME_PARENT, values.view.getParent());
}
/**
* Returns whether the view is 'visible' according to the given values
* object. This is determined by testing the same properties in the values
* object that are used to determine whether the object is appearing or
* disappearing in the {@link
* #play(android.view.ViewGroup, TransitionValues, TransitionValues)}
* method. This method can be called by, for example, subclasses that want
* to know whether the object is visible in the same way that Visibility
* determines it for the actual animation.
*
* @param values The TransitionValues object that holds the information by
* which visibility is determined.
* @return True if the view reference by <code>values</code> is visible,
* false otherwise.
*/
public boolean isVisible(TransitionValues values) {
if (values == null) {
return false;
}
int visibility = (Integer) values.values.get(PROPNAME_VISIBILITY);
View parent = (View) values.values.get(PROPNAME_PARENT);
return visibility == View.VISIBLE && parent != null;
}
private boolean isHierarchyVisibilityChanging(ViewGroup sceneRoot, ViewGroup view) {
if (view == sceneRoot) {
@ -197,5 +232,4 @@ public abstract class Visibility extends Transition {
TransitionValues endValues, int endVisibility) {
return null;
}
}