Animating the folder title & page indicator when a multi-page folder
is opened for the first time

Change-Id: I70f5fd942724251a5e863fbb78a0c24f440b0283
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index a282805..f2d7a69 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -89,6 +89,8 @@
      */
     private static final float ICON_OVERSCROLL_WIDTH_FACTOR = 0.45f;
 
+    public static final int FOOTER_ANIMATION_DURATION = 200;
+
     private static final int REORDER_DELAY = 250;
     private static final int ON_EXIT_CLOSE_DELAY = 400;
     private static final Rect sTempRect = new Rect();
@@ -201,10 +203,7 @@
                 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
 
         mFooter = findViewById(R.id.folder_footer);
-        updateFooterHeight();
-    }
 
-    public void updateFooterHeight() {
         // We find out how tall footer wants to be (it is set to wrap_content), so that
         // we can allocate the appropriate amount of space for it.
         int measureSpec = MeasureSpec.UNSPECIFIED;
@@ -529,6 +528,36 @@
                 mContent.setFocusOnFirstChild();
             }
         });
+
+        // Footer animation
+        if (mContent.getPageCount() > 1 && !mInfo.hasOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION)) {
+            int footerWidth = mContent.getDesiredWidth()
+                    - mFooter.getPaddingLeft() - mFooter.getPaddingRight();
+
+            float textWidth =  mFolderName.getPaint().measureText(mFolderName.getText().toString());
+            mFolderName.setTranslationX((footerWidth - textWidth) / 2);
+            mContent.setMarkerScale(0);
+
+            // Do not update the flag if we are in drag mode. The flag will be updated, when we
+            // actually drop the icon.
+            final boolean updateAnimationFlag = !mDragInProgress;
+            openFolderAnim.addListener(new AnimatorListenerAdapter() {
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mFolderName.animate().setDuration(FOOTER_ANIMATION_DURATION).translationX(0);
+                    mContent.animateMarkers();
+
+                    if (updateAnimationFlag) {
+                        mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher);
+                    }
+                }
+            });
+        } else {
+            mFolderName.setTranslationX(0);
+            mContent.setMarkerScale(1);
+        }
+
         openFolderAnim.start();
 
         // Make sure the folder picks up the last drag move even if the finger doesn't move.
@@ -805,6 +834,14 @@
         // Reordering may have occured, and we need to save the new item locations. We do this once
         // at the end to prevent unnecessary database operations.
         updateItemLocationsInDatabaseBatch();
+
+        // Use the item count to check for multi-page as the folder UI may not have
+        // been refreshed yet.
+        if (getItemCount() <= mContent.itemsPerPage()) {
+            // Show the animation, next time something is added to the folder.
+            mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false, mLauncher);
+        }
+
     }
 
     @Override
@@ -1183,6 +1220,11 @@
         // Clear the drag info, as it is no longer being dragged.
         mCurrentDragInfo = null;
         mDragInProgress = false;
+
+        if (mContent.getPageCount() > 1) {
+            // The animation has already been shown while opening the folder.
+            mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher);
+        }
     }
 
     // This is used so the item doesn't immediately appear in the folder when added. In one case
@@ -1197,6 +1239,7 @@
         v.setVisibility(VISIBLE);
     }
 
+    @Override
     public void onAdd(ShortcutInfo item) {
         // If the item was dropped onto this open folder, we have done the work associated
         // with adding the item to the folder, as indicated by mSuppressOnAdd being set
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 80b1564..aea21c9 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -42,6 +42,11 @@
     public static final int FLAG_WORK_FOLDER = 0x00000002;
 
     /**
+     * The multi-page animation has run for this folder
+     */
+    public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
+
+    /**
      * Whether this folder has been opened
      */
     boolean opened;
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index a1c909a..f070a6b 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -20,9 +20,11 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.DecelerateInterpolator;
+import android.view.animation.OvershootInterpolator;
 
 import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
 import com.android.launcher3.PageIndicator.PageMarkerResources;
@@ -44,6 +46,8 @@
     private static final int START_VIEW_REORDER_DELAY = 30;
     private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
 
+    private static final int PAGE_INDICATOR_ANIMATION_DELAY = 150;
+
     /**
      * Fraction of the width to scroll when showing the next page hint.
      */
@@ -73,7 +77,7 @@
     private FocusIndicatorView mFocusIndicatorView;
     private PagedFolderKeyEventListener mKeyListener;
 
-    private View mPageIndicator;
+    private PageIndicator mPageIndicator;
 
     public FolderPagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -101,7 +105,7 @@
         mFolder = folder;
         mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
         mKeyListener = new PagedFolderKeyEventListener(folder);
-        mPageIndicator = folder.findViewById(R.id.folder_page_indicator);
+        mPageIndicator = (PageIndicator) folder.findViewById(R.id.folder_page_indicator);
     }
 
     /**
@@ -338,11 +342,8 @@
         setEnableOverscroll(getPageCount() > 1);
 
         // Update footer
-        int indicatorVisibility = mPageIndicator.getVisibility();
         mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE);
-        if (indicatorVisibility != mPageIndicator.getVisibility()) {
-            mFolder.updateFooterHeight();
-        }
+        mFolder.mFolderName.setGravity(getPageCount() > 1 ? Gravity.START : Gravity.CENTER_HORIZONTAL);
     }
 
     public int getDesiredWidth() {
@@ -628,4 +629,29 @@
             }
         }
     }
+
+    public void setMarkerScale(float scale) {
+        int count  = mPageIndicator.getChildCount();
+        for (int i = 0; i < count; i++) {
+            View marker = mPageIndicator.getChildAt(i);
+            marker.animate().cancel();
+            marker.setScaleX(scale);
+            marker.setScaleY(scale);
+        }
+    }
+
+    public void animateMarkers() {
+        int count  = mPageIndicator.getChildCount();
+        OvershootInterpolator interpolator = new OvershootInterpolator(4);
+        for (int i = 0; i < count; i++) {
+            mPageIndicator.getChildAt(i).animate().scaleX(1).scaleY(1)
+                .setInterpolator(interpolator)
+                .setDuration(Folder.FOOTER_ANIMATION_DURATION)
+                .setStartDelay(PAGE_INDICATOR_ANIMATION_DELAY * i);
+        }
+    }
+
+    public int itemsPerPage() {
+        return mMaxItemsPerPage;
+    }
 }