Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 199acdfc authored by Chet Haase's avatar Chet Haase
Browse files

Better Transition interruption

Previously, a running transition on a scene root would simply
be canceled when a new transition was started. This would result in
abrupt scene changes, especially in generic use cases where apps/widgets
would spawn multiple transitions in successive rendering frames due to
small changes in view properties.

The new approach is to check all running animations against new transitions.
If there are overlapping properties that are being set to different values,
the new animations win and the old ones are canceled. If the end values are the
same, the new animations are noop'd and the old ones are allowed to continue
as-is.

There was also improvement to capturing state while other transitions are
running, necessary in this new world where old transitions are allowed to
continue running. Now, transitions are pause()'d while values are captured,
then resume()'d after capturing is done. This allows the system to see what the
real view properties are, instead of the mid-animation values.

Change-Id: I8e77fb9c1967087a682bb26a45763005f5ca9179
parent d3135451
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -29492,19 +29492,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);
@@ -29517,6 +29516,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);
  }
@@ -29563,6 +29564,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);
  }
}
+12 −0
Original line number Diff line number Diff line
@@ -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}
     */
+87 −20
Original line number Diff line number Diff line
@@ -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;
        // if alpha < 1, just fade it in from the current value
        if (endView.getAlpha() == 1.0f) {
            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);
        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) {
                    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;
    }
+60 −8
Original line number Diff line number Diff line
@@ -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
Loading