Add animation for expand/collapse actions in editor

Add sliding and fade in animations for the structured names and address
editor fields that have expand/collapse actions.

Bug: 6009430
Change-Id: I005bcf4cbe9c8103fbbece506547551adf49d2ae
diff --git a/src/com/android/contacts/editor/EditorAnimator.java b/src/com/android/contacts/editor/EditorAnimator.java
index 5196f3f..7e85d8b 100644
--- a/src/com/android/contacts/editor/EditorAnimator.java
+++ b/src/com/android/contacts/editor/EditorAnimator.java
@@ -80,6 +80,41 @@
         });
     }
 
+    /**
+     * Slides the view into its new height, while simultaneously fading it into view.
+     *
+     * @param target The target view to perform the animation on.
+     * @param previousHeight The previous height of the view before its height was changed.
+     * Needed because the view does not store any state information about its previous height.
+     */
+    public void slideAndFadeIn(final ViewGroup target, final int previousHeight) {
+        mRunner.endOldAnimation();
+        target.setVisibility(View.VISIBLE);
+        target.setAlpha(0.0f);
+        target.requestFocus();
+        SchedulingUtils.doAfterLayout(target, new Runnable() {
+            @Override
+            public void run() {
+                final int offset = target.getHeight() - previousHeight;
+                final List<Animator> animators = Lists.newArrayList();
+
+                // Translations
+                final List<View> viewsToMove = getViewsBelowOf(target);
+
+                translateViews(animators, viewsToMove, -offset, 0.0f, 0, 200);
+
+                // Fade in
+                final ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat(
+                        target, View.ALPHA, 0.0f, 1.0f);
+                fadeInAnimator.setDuration(200);
+                fadeInAnimator.setStartDelay(200);
+                animators.add(fadeInAnimator);
+
+                mRunner.run(animators);
+            }
+        });
+    }
+
     public void expandOrganization(final View addOrganizationButton,
             final ViewGroup organizationSectionViewContainer) {
         mRunner.endOldAnimation();
@@ -93,7 +128,6 @@
                 // How many pixels extra do we need?
                 final int offset = organizationSectionViewContainer.getHeight() -
                         addOrganizationButton.getHeight();
-
                 final List<Animator> animators = Lists.newArrayList();
 
                 // Fade out
@@ -105,7 +139,6 @@
                 // Translations
                 final List<View> viewsToMove = getViewsBelowOf(organizationSectionViewContainer);
                 translateViews(animators, viewsToMove, -offset, 0.0f, 0, 200);
-
                 // Fade in
                 final ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat(
                         organizationSectionViewContainer, View.ALPHA, 0.0f, 1.0f);
@@ -198,8 +231,7 @@
     }
 
     /**
-     * Traverses up the view hierarchy and returns all views below this item. Stops
-     * once a parent is not a vertical LinearLayout
+     * Traverses up the view hierarchy and returns all views physically below this item.
      *
      * @return List of views that are below the given view. Empty list if parent of view is null.
      */
@@ -208,24 +240,28 @@
         final List<View> result = Lists.newArrayList();
         if (victimParent != null) {
             final int index = victimParent.indexOfChild(view);
-            getViewsBelowOfRecursive(result, victimParent, index + 1);
+            getViewsBelowOfRecursive(result, victimParent, index + 1, view);
         }
         return result;
     }
 
     private static void getViewsBelowOfRecursive(List<View> result, ViewGroup container,
-            int index) {
+            int index, View target) {
         for (int i = index; i < container.getChildCount(); i++) {
-            result.add(container.getChildAt(i));
+            View view = container.getChildAt(i);
+            // consider the child view below the target view only if it is physically
+            // below the view on-screen, using half the height of the target view as the
+            // baseline
+            if (view.getY() > (target.getY() + target.getHeight() / 2)) {
+                result.add(view);
+            }
         }
 
         final ViewParent parent = container.getParent();
         if (parent instanceof LinearLayout) {
             final LinearLayout parentLayout = (LinearLayout) parent;
-            if (parentLayout.getOrientation() == LinearLayout.VERTICAL) {
-                int containerIndex = parentLayout.indexOfChild(container);
-                getViewsBelowOfRecursive(result, parentLayout, containerIndex+1);
-            }
+            int containerIndex = parentLayout.indexOfChild(container);
+            getViewsBelowOfRecursive(result, parentLayout, containerIndex + 1, target);
         }
     }
 
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index 0c52a2a..0b47021 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -58,6 +58,7 @@
     private boolean mHideOptional = true;
     private boolean mHasShortAndLongForms;
     private int mMinFieldHeight;
+    private int mPreviousViewHeight;
 
     public TextFieldsEditorView(Context context) {
         super(context);
@@ -87,6 +88,8 @@
         mExpansionViewContainer.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
+                mPreviousViewHeight = mFields.getHeight();
+
                 // Save focus
                 final View focusedChild = getFocusedChild();
                 final int focusedViewId = focusedChild == null ? -1 : focusedChild.getId();
@@ -103,6 +106,8 @@
                     newFocusView = TextFieldsEditorView.this;
                 }
                 newFocusView.requestFocus();
+
+                EditorAnimator.getInstance().slideAndFadeIn(mFields, mPreviousViewHeight);
             }
         });
     }