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

Commit 0d29936e authored by Chet Haase's avatar Chet Haase
Browse files

Fix bug in LayoutTransition for INVISIBLE views

When a view is becoming VISIBLE or INVISIBLE in a container with a
LayoutTransition, animations run to fade the view in and out and also
to run 'changing' animations on the view's other siblings. This logic
also cancels any running 'changin' animations to account for new ones
running.

However, in the specific case of INVISIBLE changes, there will be no
layout changes in the container - layout has already accounted for that
view (unlike in the case of GONE views); the visibility is just a matter of
drawing the view (or not). Therefore, we're canceling 'changing' animations
that should continue running and not replacing them with any other animations,
since new animations would only be started on layout chnages which are not
forthcoming.

One artifact seen from this bug is that the navigation bar buttons sometimes
disappear when changing orientation. This is because the menu button may
toggle between VISIBLE and INVISIBLE, causing animations on the other
buttons to get canceled, which leaves those views in a completely wrong
state.

The right thing to do is to avoid canceling in-process 'changing' animations
and to skip the logic of setting up new 'changing' animations which won't fire
anyway.

There is some minor API work in here because we did not previously have the
necessary information in LayoutTransition to know whether a view was being
hidden or shown to/from the INVISIBLE state.

Issue #5911213: LayoutTransitions ending in an odd state

Change-Id: I5c60c8583c8ea08965727b4ef17b550c40a3882c
parent acabf488
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -2288,7 +2288,8 @@ package android.animation {
    method public long getStagger(int);
    method public long getStartDelay(int);
    method public java.util.List<android.animation.LayoutTransition.TransitionListener> getTransitionListeners();
    method public void hideChild(android.view.ViewGroup, android.view.View);
    method public deprecated void hideChild(android.view.ViewGroup, android.view.View);
    method public void hideChild(android.view.ViewGroup, android.view.View, int);
    method public boolean isChangingLayout();
    method public boolean isRunning();
    method public void removeChild(android.view.ViewGroup, android.view.View);
@@ -2300,7 +2301,8 @@ package android.animation {
    method public void setInterpolator(int, android.animation.TimeInterpolator);
    method public void setStagger(int, long);
    method public void setStartDelay(int, long);
    method public void showChild(android.view.ViewGroup, android.view.View);
    method public deprecated void showChild(android.view.ViewGroup, android.view.View);
    method public void showChild(android.view.ViewGroup, android.view.View, int);
    field public static final int APPEARING = 2; // 0x2
    field public static final int CHANGE_APPEARING = 0; // 0x0
    field public static final int CHANGE_DISAPPEARING = 1; // 0x1
+70 −10
Original line number Diff line number Diff line
@@ -1024,18 +1024,25 @@ public class LayoutTransition {
     *
     * @param parent The ViewGroup to which the View is being added.
     * @param child The View being added to the ViewGroup.
     * @param changesLayout Whether the removal will cause changes in the layout of other views
     * in the container. INVISIBLE views becoming VISIBLE will not cause changes and thus will not
     * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
     */
    public void addChild(ViewGroup parent, View child) {
    private void addChild(ViewGroup parent, View child, boolean changesLayout) {
        // Want disappearing animations to finish up before proceeding
        cancel(DISAPPEARING);
        if (changesLayout) {
            // Also, cancel changing animations so that we start fresh ones from current locations
            cancel(CHANGE_APPEARING);
        }
        if (mListeners != null) {
            for (TransitionListener listener : mListeners) {
                listener.startTransition(this, parent, child, APPEARING);
            }
        }
        if (changesLayout) {
            runChangeTransition(parent, child, APPEARING);
        }
        runAppearingTransition(parent, child);
    }

@@ -1048,8 +1055,31 @@ public class LayoutTransition {
     * @param parent The ViewGroup to which the View is being added.
     * @param child The View being added to the ViewGroup.
     */
    public void addChild(ViewGroup parent, View child) {
        addChild(parent, child, true);
    }

    /**
     * @deprecated Use {@link #showChild(android.view.ViewGroup, android.view.View, int)}.
     */
    @Deprecated
    public void showChild(ViewGroup parent, View child) {
        addChild(parent, child);
        addChild(parent, child, true);
    }

    /**
     * This method is called by ViewGroup when a child view is about to be made visible in the
     * container. This callback starts the process of a transition; we grab the starting
     * values, listen for changes to all of the children of the container, and start appropriate
     * animations.
     *
     * @param parent The ViewGroup in which the View is being made visible.
     * @param child The View being made visible.
     * @param oldVisibility The previous visibility value of the child View, either
     * {@link View#GONE} or {@link View#INVISIBLE}.
     */
    public void showChild(ViewGroup parent, View child, int oldVisibility) {
        addChild(parent, child, oldVisibility == View.GONE);
    }

    /**
@@ -1060,18 +1090,25 @@ public class LayoutTransition {
     *
     * @param parent The ViewGroup from which the View is being removed.
     * @param child The View being removed from the ViewGroup.
     * @param changesLayout Whether the removal will cause changes in the layout of other views
     * in the container. Views becoming INVISIBLE will not cause changes and thus will not
     * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
     */
    public void removeChild(ViewGroup parent, View child) {
    private void removeChild(ViewGroup parent, View child, boolean changesLayout) {
        // Want appearing animations to finish up before proceeding
        cancel(APPEARING);
        if (changesLayout) {
            // Also, cancel changing animations so that we start fresh ones from current locations
            cancel(CHANGE_DISAPPEARING);
        }
        if (mListeners != null) {
            for (TransitionListener listener : mListeners) {
                listener.startTransition(this, parent, child, DISAPPEARING);
            }
        }
        if (changesLayout) {
            runChangeTransition(parent, child, DISAPPEARING);
        }
        runDisappearingTransition(parent, child);
    }

@@ -1084,8 +1121,31 @@ public class LayoutTransition {
     * @param parent The ViewGroup from which the View is being removed.
     * @param child The View being removed from the ViewGroup.
     */
    public void removeChild(ViewGroup parent, View child) {
        removeChild(parent, child, true);
    }

    /**
     * @deprecated Use {@link #hideChild(android.view.ViewGroup, android.view.View, int)}.
     */
    @Deprecated
    public void hideChild(ViewGroup parent, View child) {
        removeChild(parent, child);
        removeChild(parent, child, true);
    }

    /**
     * This method is called by ViewGroup when a child view is about to be hidden in
     * container. This callback starts the process of a transition; we grab the starting
     * values, listen for changes to all of the children of the container, and start appropriate
     * animations.
     *
     * @param parent The parent ViewGroup of the View being hidden.
     * @param child The View being hidden.
     * @param newVisibility The new visibility value of the child View, either
     * {@link View#GONE} or {@link View#INVISIBLE}.
     */
    public void hideChild(ViewGroup parent, View child, int newVisibility) {
        removeChild(parent, child, newVisibility == View.GONE);
    }

    /**
+2 −1
Original line number Diff line number Diff line
@@ -6822,7 +6822,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
        if ((changed & VISIBILITY_MASK) != 0) {
            if (mParent instanceof ViewGroup) {
                ((ViewGroup) mParent).onChildVisibilityChanged(this, (flags & VISIBILITY_MASK));
                ((ViewGroup) mParent).onChildVisibilityChanged(this, (changed & VISIBILITY_MASK),
                        (flags & VISIBILITY_MASK));
                ((View) mParent).invalidate(true);
            } else if (mParent != null) {
                mParent.invalidateChild(this, null);
+11 −9
Original line number Diff line number Diff line
@@ -888,18 +888,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
    }

    /**
     * Called when a view's visibility has changed. Notify the parent to take any appropriate
     * action.
     *
     * @param child The view whose visibility has changed
     * @param oldVisibility The previous visibility value (GONE, INVISIBLE, or VISIBLE).
     * @param newVisibility The new visibility value (GONE, INVISIBLE, or VISIBLE).
     * @hide
     * @param child
     * @param visibility
     */
    protected void onChildVisibilityChanged(View child, int visibility) {
    protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
        if (mTransition != null) {
            if (visibility == VISIBLE) {
                mTransition.showChild(this, child);
            if (newVisibility == VISIBLE) {
                mTransition.showChild(this, child, oldVisibility);
            } else {
                mTransition.hideChild(this, child);
            }
            if (visibility != VISIBLE) {
                mTransition.hideChild(this, child, newVisibility);
                // Only track this on disappearing views - appearing views are already visible
                // and don't need special handling during drawChild()
                if (mVisibilityChangingChildren == null) {
@@ -914,7 +916,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager

        // in all cases, for drags
        if (mCurrentDrag != null) {
            if (visibility == VISIBLE) {
            if (newVisibility == VISIBLE) {
                notifyChildOfDrag(child);
            }
        }
+5 −3
Original line number Diff line number Diff line
@@ -842,10 +842,12 @@ public class GridLayout extends ViewGroup {
     * @hide
     */
    @Override
    protected void onChildVisibilityChanged(View child, int visibility) {
        super.onChildVisibilityChanged(child, visibility);
    protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
        super.onChildVisibilityChanged(child, oldVisibility, newVisibility);
        if (oldVisibility == GONE || newVisibility == GONE) {
            invalidateStructure();
        }
    }

    // Measurement