add collapse animation to tab switcher

    Bug: 5087355
    if the tab is not the last tab, the tabs collapse smoothly
    use animation when close button is used

Change-Id: I5dabcbb30317ff634aafd21b6f1b0e2b902b767e
diff --git a/src/com/android/browser/NavScreen.java b/src/com/android/browser/NavScreen.java
index f52ef88..a361136 100644
--- a/src/com/android/browser/NavScreen.java
+++ b/src/com/android/browser/NavScreen.java
@@ -247,7 +247,7 @@
                 @Override
                 public void onClick(View v) {
                     if (tabview.isClose(v)) {
-                        onCloseTab(tab);
+                        mScroller.animateOut(tabview);
                     } else if (tabview.isTitle(v)) {
                         mScroller.setSelection(position);
                         switchToSelected();
diff --git a/src/com/android/browser/NavTabGallery.java b/src/com/android/browser/NavTabGallery.java
index 0cd1f82..8247a5d 100644
--- a/src/com/android/browser/NavTabGallery.java
+++ b/src/com/android/browser/NavTabGallery.java
@@ -40,6 +40,7 @@
 
     private OnRemoveListener mRemoveListener;
     private boolean mBlockUpCallback;
+    private Animator mAnimator;
 
     public NavTabGallery(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
@@ -76,13 +77,15 @@
     @Override
     protected void onOrthoDrag(View v, MotionEvent down, MotionEvent move,
             float distance) {
-        offsetView(v, - distance);
+        if (mAnimator == null) {
+            offsetView(v, - distance);
+        }
     }
 
     @Override
     protected void onOrthoFling(View v, MotionEvent down, MotionEvent move,
             float velocity) {
-        if (Math.abs(velocity) > MIN_VELOCITY) {
+        if ((mAnimator == null) && (Math.abs(velocity) > MIN_VELOCITY)) {
             mBlockUpCallback = true;
             animateOut(v, velocity);
         }
@@ -90,6 +93,7 @@
 
     @Override
     protected void onUp(View downView) {
+        if (mAnimator != null) return;
         if (mBlockUpCallback) {
             mBlockUpCallback = false;
             return;
@@ -118,8 +122,13 @@
         }
     }
 
-    private void animateOut(View v, float velocity) {
-        final int position = mDownTouchPosition;
+    protected void animateOut(View v) {
+        animateOut(v, -MIN_VELOCITY);
+    }
+
+    private void animateOut(final View v, float velocity) {
+        if (mAnimator != null) return;
+        final int position = mFirstPosition + indexOfChild(v);
         int target = 0;
         if (velocity < 0) {
             target = mHorizontal ? -v.getHeight() :  - v.getWidth();
@@ -128,21 +137,28 @@
         }
         int distance = target - (mHorizontal ? v.getTop() : v.getLeft());
         long duration = (long) (Math.abs(distance) * 1000 / Math.abs(velocity));
-        ObjectAnimator animator = null;
         if (mHorizontal) {
-            animator = ObjectAnimator.ofFloat(v, TRANSLATION_Y, 0, target);
+            mAnimator = ObjectAnimator.ofFloat(v, TRANSLATION_Y, 0, target);
         } else {
-            animator = ObjectAnimator.ofFloat(v, TRANSLATION_X, 0, target);
+            mAnimator = ObjectAnimator.ofFloat(v, TRANSLATION_X, 0, target);
         }
-        animator.setDuration(duration);
-        animator.addListener(new AnimatorListenerAdapter() {
+        mAnimator.setDuration(duration);
+        mAnimator.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator a) {
                 if (mRemoveListener !=  null) {
+                    boolean needsGap = position < (mAdapter.getCount() - 1);
+                    if (needsGap) {
+                        setGapPosition(position, mHorizontal ? v.getWidth() : v.getHeight());
+                    }
                     mRemoveListener.onRemovePosition(position);
+                    if (!needsGap && position > 0) {
+                        scrollToChild(position - 1);
+                    }
+                    mAnimator = null;
                 }
             }
         });
-        animator.start();
+        mAnimator.start();
     }
 
 }
diff --git a/src/com/android/browser/view/Gallery.java b/src/com/android/browser/view/Gallery.java
index d9c172e..59e710d 100644
--- a/src/com/android/browser/view/Gallery.java
+++ b/src/com/android/browser/view/Gallery.java
@@ -16,6 +16,9 @@
 
 package com.android.browser.view;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.database.DataSetObserver;
@@ -64,7 +67,7 @@
     private RecycleBin mRecycler;
 
     protected boolean mHorizontal;
-    private int mFirstPosition;
+    protected int mFirstPosition;
     private int mItemCount;
     private boolean mDataChanged;
 
@@ -89,6 +92,10 @@
     private OnItemSelectedListener mOnItemSelectedListener;
     private SelectionNotifier mSelectionNotifier;
 
+    private int mGapPosition;
+    private int mGap;
+    private Animator mGapAnimator;
+
     /**
      * Sets mSuppressSelectionChanged = false. This is used to set it to false
      * in the future. It will also trigger a selection changed.
@@ -161,6 +168,10 @@
         mTouchSlop = configuration.getScaledTouchSlop();
         setFocusable(true);
         setWillNotDraw(false);
+        mGapPosition = INVALID_POSITION;
+        mGap = 0;
+        // proguard
+        setGap(getGap());
     }
 
     /**
@@ -192,6 +203,29 @@
         requestLayout();
     }
 
+    /**
+     * define a visual gap in the list of items
+     * the gap is rendered in front (left or above)
+     * the given position
+     * @param position
+     * @param gap
+     */
+    public void setGapPosition(int position, int gap) {
+        mGapPosition = position;
+        mGap = gap;
+    }
+
+    public void setGap(int gap) {
+        if (mGapPosition != INVALID_POSITION) {
+            mGap = gap;
+            layout(0, false);
+        }
+    }
+
+    public int getGap() {
+        return mGap;
+    }
+
     public void setAdapter(BaseAdapter adapter) {
         mAdapter = adapter;
         if (mAdapter != null) {
@@ -214,6 +248,9 @@
 
     void handleDataChanged() {
         if (mAdapter != null) {
+            if (mGapAnimator != null) {
+                mGapAnimator.cancel();
+            }
             resetList();
             mItemCount = mAdapter.getCount();
             // checkFocus();
@@ -226,7 +263,21 @@
                 // Nothing selected
                 checkSelectionChanged();
             }
-            layout(0, false);
+            if (mGapPosition > INVALID_POSITION) {
+                mGapAnimator = ObjectAnimator.ofInt(this, "gap", mGap, 0);
+                mGapAnimator.setDuration(250);
+                mGapAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator a) {
+                        mGapPosition = INVALID_POSITION;
+                        mGap = 0;
+                        mGapAnimator = null;
+                    }
+                });
+                mGapAnimator.start();
+            } else {
+                layout(0, false);
+            }
         } else {
             // checkFocus();
             mOldSelectedPosition = INVALID_POSITION;
@@ -373,7 +424,7 @@
      * @param deltaX
      *            Change in X from the previous event.
      */
-    void trackMotionScroll(int deltaX) {
+    protected void trackMotionScroll(int deltaX) {
         if (getChildCount() == 0) {
             return;
         }
@@ -587,6 +638,9 @@
         }
         fillToGalleryRight();
         fillToGalleryLeft();
+        if (mGapPosition > INVALID_POSITION) {
+            adjustGap();
+        }
         mRecycler.clear();
         invalidate();
         checkSelectionChanged();
@@ -594,6 +648,19 @@
         updateSelectedItemMetadata();
     }
 
+    void adjustGap() {
+        for (int i = 0; i < getChildCount(); i++) {
+            int pos = i + mFirstPosition;
+            if (pos >= mGapPosition) {
+                if (mHorizontal) {
+                    getChildAt(i).offsetLeftAndRight(mGap);
+                } else {
+                    getChildAt(i).offsetTopAndBottom(mGap);
+                }
+            }
+        }
+    }
+
     void recycleAllViews() {
         final int childCount = getChildCount();
         final RecycleBin recycleBin = mRecycler;
@@ -619,7 +686,7 @@
             // No children available!
             curPosition = 0;
             curRightEdge = (mHorizontal ? mRight - mLeft - mPaddingRight
-                    : mBottom - mBottom - mPaddingBottom);
+                    : mBottom - mTop - mPaddingBottom);
             mShouldStopFling = true;
         }
         while (curRightEdge > galleryLeft && curPosition >= 0) {
@@ -1122,7 +1189,7 @@
         }
     }
 
-    private boolean scrollToChild(int childPosition) {
+    protected boolean scrollToChild(int childPosition) {
         View child = getChildAt(childPosition);
         if (child != null) {
             int distance = getCenterOfGallery() - getCenterOfView(child);