Add ability to automate animated transitions on View show/hide

Change-Id: Id6ff92c8fd06c3f5fb30c41b020b4de4f567154f
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 69ad67e..8f087d5 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -544,6 +544,9 @@
                         mChangingAppearingAnim.clone() :
                         mChangingDisappearingAnim.clone();
 
+                // Cache the animation in case we need to cancel it later
+                currentAnimations.put(child, anim);
+
                 // Set the target object for the animation
                 anim.setTarget(child);
 
@@ -553,13 +556,10 @@
 
                 // Add a listener to track layout changes on this view. If we don't get a callback,
                 // then there's nothing to animate.
-                View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
+                final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
                     public void onLayoutChange(View v, int left, int top, int right, int bottom,
                             int oldLeft, int oldTop, int oldRight, int oldBottom) {
 
-                        // Cache the animation in case we need to cancel it later
-                        currentAnimations.put(child, anim);
-
                         // Tell the animation to extract end values from the changed object
                         anim.setupEndValues();
 
@@ -577,19 +577,6 @@
                         anim.setStartDelay(startDelay);
                         anim.setDuration(duration);
 
-                        // Remove the animation from the cache when it ends
-                        anim.addListener(new AnimatorListenerAdapter() {
-                            private boolean canceled = false;
-                            public void onAnimationCancel(Animator animator) {
-                                // we remove canceled animations immediately, not here
-                                canceled = true;
-                            }
-                            public void onAnimationEnd(Animator animator) {
-                                if (!canceled) {
-                                    currentAnimations.remove(child);
-                                }
-                            }
-                        });
                         if (anim instanceof ObjectAnimator) {
                             ((ObjectAnimator) anim).setCurrentPlayTime(0);
                         }
@@ -601,6 +588,22 @@
                         layoutChangeListenerMap.remove(child);
                     }
                 };
+                // Remove the animation from the cache when it ends
+                anim.addListener(new AnimatorListenerAdapter() {
+                    private boolean canceled = false;
+                    public void onAnimationCancel(Animator animator) {
+                        // we remove canceled animations immediately, not here
+                        canceled = true;
+                        child.removeOnLayoutChangeListener(listener);
+                        layoutChangeListenerMap.remove(child);
+                    }
+                    public void onAnimationEnd(Animator animator) {
+                        if (!canceled) {
+                            currentAnimations.remove(child);
+                        }
+                    }
+                });
+
                 child.addOnLayoutChangeListener(listener);
                 // cache the listener for later removal
                 layoutChangeListenerMap.put(child, listener);
@@ -662,7 +665,8 @@
         anim.setTarget(child);
         if (mListeners != null) {
             anim.addListener(new AnimatorListenerAdapter() {
-                public void onAnimationEnd() {
+                @Override
+                public void onAnimationEnd(Animator anim) {
                     for (TransitionListener listener : mListeners) {
                         listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
                     }
@@ -684,7 +688,7 @@
      * @param parent The ViewGroup to which the View is being added.
      * @param child The View being added to the ViewGroup.
      */
-    public void childAdd(ViewGroup parent, View child) {
+    public void addChild(ViewGroup parent, View child) {
         if (mListeners != null) {
             for (TransitionListener listener : mListeners) {
                 listener.startTransition(this, parent, child, APPEARING);
@@ -695,6 +699,19 @@
     }
 
     /**
+     * This method is called by ViewGroup when a child view is about to be added to 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 to which the View is being added.
+     * @param child The View being added to the ViewGroup.
+     */
+    public void showChild(ViewGroup parent, View child) {
+        addChild(parent, child);
+    }
+
+    /**
      * This method is called by ViewGroup when a child view is about to be removed from 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
@@ -703,7 +720,7 @@
      * @param parent The ViewGroup from which the View is being removed.
      * @param child The View being removed from the ViewGroup.
      */
-    public void childRemove(ViewGroup parent, View child) {
+    public void removeChild(ViewGroup parent, View child) {
         if (mListeners != null) {
             for (TransitionListener listener : mListeners) {
                 listener.startTransition(this, parent, child, DISAPPEARING);
@@ -714,6 +731,19 @@
     }
 
     /**
+     * This method is called by ViewGroup when a child view is about to be removed from 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 from which the View is being removed.
+     * @param child The View being removed from the ViewGroup.
+     */
+    public void hideChild(ViewGroup parent, View child) {
+        removeChild(parent, child);
+    }
+
+    /**
      * Add a listener that will be called when the bounds of the view change due to
      * layout processing.
      *
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4c01f4f..1dfd2bf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4905,6 +4905,9 @@
         }
 
         if ((changed & VISIBILITY_MASK) != 0) {
+            if (mParent instanceof ViewGroup) {
+                ((ViewGroup)mParent).onChildVisibilityChanged(this, (flags & VISIBILITY_MASK));
+            }
             dispatchVisibilityChanged(this, (flags & VISIBILITY_MASK));
         }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 8a3db38..6c57e1e 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -323,6 +323,10 @@
     // being animated.
     private ArrayList<View> mTransitioningViews;
 
+    // List of children changing visibility. This is used to potentially keep rendering
+    // views during a transition when they otherwise would have become gone/invisible
+    private ArrayList<View> mVisibilityChangingChildren;
+
     public ViewGroup(Context context) {
         super(context);
         initViewGroup();
@@ -758,6 +762,32 @@
     }
 
     /**
+     * @hide
+     * @param child
+     * @param visibility
+     */
+    void onChildVisibilityChanged(View child, int visibility) {
+        if (mTransition != null) {
+            if (visibility == VISIBLE) {
+                mTransition.showChild(this, child);
+            } else {
+                mTransition.hideChild(this, child);
+            }
+            if (visibility != VISIBLE) {
+                // Only track this on disappearing views - appearing views are already visible
+                // and don't need special handling during drawChild()
+                if (mVisibilityChangingChildren == null) {
+                    mVisibilityChangingChildren = new ArrayList<View>();
+                }
+                mVisibilityChangingChildren.add(child);
+                if (mTransitioningViews != null && mTransitioningViews.contains(child)) {
+                    addDisappearingView(child);
+                }
+            }
+        }
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
@@ -2598,7 +2628,7 @@
         }
 
         if (mTransition != null) {
-            mTransition.childAdd(this, child);
+            mTransition.addChild(this, child);
         }
 
         if (!checkLayoutParams(params)) {
@@ -2817,7 +2847,7 @@
     private void removeViewInternal(int index, View view) {
 
         if (mTransition != null) {
-            mTransition.childRemove(this, view);
+            mTransition.removeChild(this, view);
         }
 
         boolean clearChildFocus = false;
@@ -2893,7 +2923,7 @@
             final View view = children[i];
 
             if (mTransition != null) {
-                mTransition.childRemove(this, view);
+                mTransition.removeChild(this, view);
             }
 
             if (view == focused) {
@@ -2962,7 +2992,7 @@
             final View view = children[i];
 
             if (mTransition != null) {
-                mTransition.childRemove(this, view);
+                mTransition.removeChild(this, view);
             }
 
             if (view == focused) {
@@ -3005,7 +3035,7 @@
      */
     protected void removeDetachedView(View child, boolean animate) {
         if (mTransition != null) {
-            mTransition.childRemove(this, child);
+            mTransition.removeChild(this, child);
         }
 
         if (child == mFocused) {
@@ -4025,11 +4055,16 @@
             final ArrayList<View> disappearingChildren = mDisappearingChildren;
             if (disappearingChildren != null && disappearingChildren.contains(view)) {
                 disappearingChildren.remove(view);
-                if (view.mAttachInfo != null) {
-                    view.dispatchDetachedFromWindow();
-                }
-                if (view.mParent != null) {
-                    view.mParent = null;
+                if (mVisibilityChangingChildren != null &&
+                        mVisibilityChangingChildren.contains(view)) {
+                    mVisibilityChangingChildren.remove(view);
+                } else {
+                    if (view.mAttachInfo != null) {
+                        view.dispatchDetachedFromWindow();
+                    }
+                    if (view.mParent != null) {
+                        view.mParent = null;
+                    }
                 }
                 mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
             }
diff --git a/core/java/android/widget/Adapter.java b/core/java/android/widget/Adapter.java
index f2b3e2a..9b6c5a4 100644
--- a/core/java/android/widget/Adapter.java
+++ b/core/java/android/widget/Adapter.java
@@ -72,7 +72,7 @@
     long getItemId(int position);
     
     /**
-     * Indicated whether the item ids are stable across changes to the
+     * Indicates whether the item ids are stable across changes to the
      * underlying data.
      * 
      * @return True if the same id always refers to the same object.
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index ab75420..f5afb94 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -172,7 +172,7 @@
     int mItemCount;
 
     /**
-     * The number of items in the adapter before a data changed event occured.
+     * The number of items in the adapter before a data changed event occurred.
      */
     int mOldItemCount;
 
@@ -557,7 +557,7 @@
     /**
      * @return The number of items owned by the Adapter associated with this
      *         AdapterView. (This is the number of data items, which may be
-     *         larger than the number of visible view.)
+     *         larger than the number of visible views.)
      */
     @ViewDebug.CapturedViewProperty
     public int getCount() {