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

Commit b7a7fc9d authored by Chet Haase's avatar Chet Haase
Browse files

Make fading transitions work better

Previously, a Fade transition would only affect a view if its
parent hierarchy was not also affected between the start/end states.
This caused problems for views which were removed from their parents
between scenes when their parents' visibility also changed between those
scenes. The effect would be that the transition would fade the parent...
but the child would no longer be in that parent, so the user would just see the
child view blink out.

This fix ensure that views are faded appropriately by fading them
regardless the parent hierarchy; if a view is removed from its
parent, fade it out.

Additionally, if that view has not been removed from its parent, but
its parent is no longer parented *and* scene being
transitioned from is based on a layout resource file (and thus
the views are considered temporary after transitioning), then it is
removed from its parent to be faded out in the overlay.

Also, renamed TextChange to ChangeText to be more consistent with
other transition class names.

Change-Id: I4e0e7dfc9e9d95c7a4ca586534b6d204c4f3bae0
parent c31f1188
Loading
Loading
Loading
Loading
+38 −16
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ import java.util.Map;
 *
 * @hide
 */
public class TextChange extends Transition {
public class ChangeText extends Transition {

    private static final String LOG_TAG = "TextChange";

@@ -103,7 +103,7 @@ public class TextChange extends Transition {
     * transition is run.
     * @return this textChange object.
     */
    public TextChange setChangeBehavior(int changeBehavior) {
    public ChangeText setChangeBehavior(int changeBehavior) {
        if (changeBehavior >= CHANGE_BEHAVIOR_KEEP && changeBehavior <= CHANGE_BEHAVIOR_OUT_IN) {
            mChangeBehavior = changeBehavior;
        }
@@ -179,10 +179,14 @@ public class TextChange extends Transition {
            startSelectionStart = startSelectionEnd = endSelectionStart = endSelectionEnd = -1;
        }
        if (!startText.equals(endText)) {
            final int startColor = (Integer) startVals.get(PROPNAME_TEXT_COLOR);
            final int endColor = (Integer) endVals.get(PROPNAME_TEXT_COLOR);
            if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
                view.setText(startText);
                if (view instanceof EditText) {
                    setSelection(((EditText) view), startSelectionStart, startSelectionEnd);
                }
            }
            Animator anim;
            if (mChangeBehavior == CHANGE_BEHAVIOR_KEEP) {
                anim = ValueAnimator.ofFloat(0, 1);
@@ -200,8 +204,6 @@ public class TextChange extends Transition {
                });
            } else {
                // Fade out start text
                final int startColor = (Integer) startVals.get(PROPNAME_TEXT_COLOR);
                final int endColor = (Integer) endVals.get(PROPNAME_TEXT_COLOR);
                ValueAnimator outAnim = null, inAnim = null;
                if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN ||
                        mChangeBehavior == CHANGE_BEHAVIOR_OUT) {
@@ -210,8 +212,8 @@ public class TextChange extends Transition {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                            int currAlpha = (Integer) animation.getAnimatedValue();
                            view.setTextColor(currAlpha << 24 | Color.red(startColor) << 16 |
                                    Color.green(startColor) << 8 | Color.red(startColor));
                            view.setTextColor(currAlpha << 24 | startColor & 0xff0000 |
                                    startColor & 0xff00 | startColor & 0xff);
                        }
                    });
                    outAnim.addListener(new AnimatorListenerAdapter() {
@@ -225,6 +227,8 @@ public class TextChange extends Transition {
                                            endSelectionEnd);
                                }
                            }
                            // restore opaque alpha and correct end color
                            view.setTextColor(endColor);
                        }
                    });
                }
@@ -239,6 +243,13 @@ public class TextChange extends Transition {
                                    Color.green(endColor) << 8 | Color.red(endColor));
                        }
                    });
                    inAnim.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationCancel(Animator animation) {
                            // restore opaque alpha and correct end color
                            view.setTextColor(endColor);
                        }
                    });
                }
                if (outAnim != null && inAnim != null) {
                    anim = new AnimatorSet();
@@ -251,23 +262,34 @@ public class TextChange extends Transition {
                }
            }
            TransitionListener transitionListener = new TransitionListenerAdapter() {
                boolean mCanceled = false;
                int mPausedColor = 0;

                @Override
                public void onTransitionPause(Transition transition) {
                    if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
                        view.setText(endText);
                        if (view instanceof EditText) {
                            setSelection(((EditText) view), endSelectionStart, endSelectionEnd);
                        }
                    }
                    if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) {
                        mPausedColor = view.getCurrentTextColor();
                        view.setTextColor(endColor);
                    }
                }

                @Override
                public void onTransitionResume(Transition transition) {
                    if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
                        view.setText(startText);
                        if (view instanceof EditText) {
                            setSelection(((EditText) view), startSelectionStart, startSelectionEnd);
                        }
                    }
                    if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) {
                        view.setTextColor(mPausedColor);
                    }
                }
            };
            addListener(transitionListener);
            if (DBG) {
+41 −4
Original line number Diff line number Diff line
@@ -30,6 +30,24 @@ import android.view.ViewGroup;
 * {@link View#setVisibility(int)} state of the view as well as whether it
 * is parented in the current view hierarchy.
 *
 * <p>The ability of this transition to fade out a particular view, and the
 * way that that fading operation takes place, is based on
 * the situation of the view in the view hierarchy. For example, if a view was
 * simply removed from its parent, then the view will be added into a {@link
 * android.view.ViewGroupOverlay} while fading. If a visible view is
 * changed to be {@link View#GONE} or {@link View#INVISIBLE}, then the
 * visibility will be changed to {@link View#VISIBLE} for the duration of
 * the animation. However, if a view is in a hierarchy which is also altering
 * its visibility, the situation can be more complicated. In general, if a
 * view that is no longer in the hierarchy in the end scene still has a
 * parent (so its parent hierarchy was removed, but it was not removed from
 * its parent), then it will be left alone to avoid side-effects from
 * improperly removing it from its parent. The only exception to this is if
 * the previous {@link Scene} was
 * {@link Scene#getSceneForLayout(android.view.ViewGroup, int, android.content.Context)
 * created from a layout resource file}, then it is considered safe to un-parent
 * the starting scene view in order to fade it out.</p>
 *
 * <p>A Fade transition can be described in a resource file by using the
 * tag <code>fade</code>, along with the standard
 * attributes of {@link android.R.styleable#Fade} and
@@ -167,7 +185,7 @@ public class Fade extends Visibility {
        if ((mFadingMode & OUT) != OUT) {
            return null;
        }
        View view;
        View view = null;
        View startView = (startValues != null) ? startValues.view : null;
        View endView = (endValues != null) ? endValues.view : null;
        if (DBG) {
@@ -177,9 +195,28 @@ public class Fade extends Visibility {
        View overlayView = null;
        View viewToKeep = null;
        if (endView == null || endView.getParent() == null) {
            // view was removed: add the start view to the Overlay
            view = startView;
            overlayView = view;
            if (endView != null) {
                // endView was removed from its parent - add it to the overlay
                view = overlayView = endView;
            } else if (startView != null) {
                // endView does not exist. Use startView only under certain
                // conditions, because placing a view in an overlay necessitates
                // it being removed from its current parent
                if (startView.getParent() == null) {
                    // no parent - safe to use
                    view = overlayView = startView;
                } else if (startView.getParent() instanceof View &&
                        startView.getParent().getParent() == null) {
                    View startParent = (View) startView.getParent();
                    int id = startParent.getId();
                    if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
                        // no parent, but its parent is unparented  but the parent
                        // hierarchy has been replaced by a new hierarchy with the same id
                        // and it is safe to un-parent startView
                        view = overlayView = startView;
                    }
                }
            }
        } else {
            // visibility change
            if (endVisibility == View.INVISIBLE) {
+17 −2
Original line number Diff line number Diff line
@@ -157,11 +157,11 @@ public final class Scene {
    public void enter() {

        // Apply layout change, if any
        if (mLayoutId >= 0 || mLayout != null) {
        if (mLayoutId > 0 || mLayout != null) {
            // empty out parent container before adding to it
            getSceneRoot().removeAllViews();

            if (mLayoutId >= 0) {
            if (mLayoutId > 0) {
                LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
            } else {
                mSceneRoot.addView(mLayout);
@@ -242,4 +242,19 @@ public final class Scene {
        mExitAction = action;
    }


    /**
     * Returns whether this Scene was created by a layout resource file, determined
     * by the layoutId passed into
     * {@link #getSceneForLayout(android.view.ViewGroup, int, android.content.Context)}.
     * This is called by TransitionManager to determine whether it is safe for views from
     * this scene to be removed from their parents when the scene is exited, which is
     * used by {@link Fade} to fade these views out (the views must be removed from
     * their parent in order to add them to the overlay for fading purposes). If a
     * Scene is not based on a resource file, then the impact of removing views
     * arbitrarily is unknown and should be avoided.
     */
    boolean isCreatedFromLayoutResource() {
        return (mLayoutId > 0);
    }
}
 No newline at end of file
+12 −0
Original line number Diff line number Diff line
@@ -118,6 +118,14 @@ public abstract class Transition implements Cloneable {
    // Scene Root is set at createAnimator() time in the cloned Transition
    ViewGroup mSceneRoot = null;

    // Whether removing views from their parent is possible. This is only for views
    // in the start scene, which are no longer in the view hierarchy. This property
    // is determined by whether the previous Scene was created from a layout
    // resource, and thus the views from the exited scene are going away anyway
    // and can be removed as necessary to achieve a particular effect, such as
    // removing them from parents to add them to overlays.
    boolean mCanRemoveViews = false;

    // Track all animators in use in case the transition gets canceled and needs to
    // cancel running animators
    private ArrayList<Animator> mCurrentAnimators = new ArrayList<Animator>();
@@ -1445,6 +1453,10 @@ public abstract class Transition implements Cloneable {
        return this;
    }

    void setCanRemoveViews(boolean canRemoveViews) {
        mCanRemoveViews = canRemoveViews;
    }

    @Override
    public String toString() {
        return toString("");
+5 −0
Original line number Diff line number Diff line
@@ -178,6 +178,11 @@ public class TransitionManager {
        Transition transitionClone = transition.clone();
        transitionClone.setSceneRoot(sceneRoot);

        Scene oldScene = Scene.getCurrentScene(sceneRoot);
        if (oldScene != null && oldScene.isCreatedFromLayoutResource()) {
            transitionClone.setCanRemoveViews(true);
        }

        sceneChangeSetup(sceneRoot, transitionClone);

        scene.enter();
Loading