diff options
Diffstat (limited to 'src/com/android/launcher3/folder/FolderAnimationManager.java')
-rw-r--r-- | src/com/android/launcher3/folder/FolderAnimationManager.java | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java new file mode 100644 index 000000000..6ce572d94 --- /dev/null +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.folder; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.GradientDrawable; +import android.support.v4.graphics.ColorUtils; +import android.util.Property; +import android.view.View; +import android.view.animation.AnimationUtils; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.CellLayout; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils; +import com.android.launcher3.R; +import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.util.Themes; + +import java.util.List; + +/** + * Manages the opening and closing animations for a {@link Folder}. + * + * All of the animations are done in the Folder. + * ie. When the user taps on the FolderIcon, we immediately hide the FolderIcon and show the Folder + * in its place before starting the animation. + */ +public class FolderAnimationManager { + + private Folder mFolder; + private FolderPagedView mContent; + private GradientDrawable mFolderBackground; + + private FolderIcon mFolderIcon; + private FolderIcon.PreviewBackground mPreviewBackground; + + private Context mContext; + private Launcher mLauncher; + + private Animator mRevealAnimator; + private final TimeInterpolator mOpeningInterpolator; + private final TimeInterpolator mClosingInterpolator; + private final TimeInterpolator mPreviewItemOpeningInterpolator; + private final TimeInterpolator mPreviewItemClosingInterpolator; + + private final FolderIcon.PreviewItemDrawingParams mTmpParams = + new FolderIcon.PreviewItemDrawingParams(0, 0, 0, 0); + + private static final Property<View, Float> SCALE_PROPERTY = + new Property<View, Float>(Float.class, "scale") { + @Override + public Float get(View view) { + return view.getScaleX(); + } + + @Override + public void set(View view, Float scale) { + view.setScaleX(scale); + view.setScaleY(scale); + } + }; + + private static final Property<List<BubbleTextView>, Integer> ITEMS_TEXT_COLOR_PROPERTY = + new Property<List<BubbleTextView>, Integer>(Integer.class, "textColor") { + @Override + public Integer get(List<BubbleTextView> items) { + return items.get(0).getCurrentTextColor(); + } + + @Override + public void set(List<BubbleTextView> items, Integer color) { + int size = items.size(); + + for (int i = 0; i < size; ++i) { + items.get(i).setTextColor(color); + } + } + }; + + public FolderAnimationManager(Folder folder) { + mFolder = folder; + mContent = folder.mContent; + mFolderBackground = (GradientDrawable) mFolder.getBackground(); + + mFolderIcon = folder.mFolderIcon; + mPreviewBackground = mFolderIcon.mBackground; + + mContext = folder.getContext(); + mLauncher = folder.mLauncher; + + mOpeningInterpolator = AnimationUtils.loadInterpolator(mContext, + R.interpolator.folder_opening_interpolator); + mClosingInterpolator = AnimationUtils.loadInterpolator(mContext, + R.interpolator.folder_closing_interpolator); + mPreviewItemOpeningInterpolator = AnimationUtils.loadInterpolator(mContext, + R.interpolator.folder_preview_item_opening_interpolator); + mPreviewItemClosingInterpolator = AnimationUtils.loadInterpolator(mContext, + R.interpolator.folder_preview_item_closing_interpolator); + } + + public AnimatorSet getOpeningAnimator() { + mFolder.setPivotX(0); + mFolder.setPivotY(0); + + AnimatorSet a = getAnimatorSet(true /* isOpening */); + a.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mFolderIcon.setVisibility(View.INVISIBLE); + } + }); + return a; + } + + public AnimatorSet getClosingAnimator() { + AnimatorSet a = getAnimatorSet(false /* isOpening */); + return a; + } + + /** + * Prepares the Folder for animating between open / closed states. + * + * @param isOpening If true, return the animator set for the opening animation. + */ + private AnimatorSet getAnimatorSet(final boolean isOpening) { + final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams(); + FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule(); + final List<BubbleTextView> itemsInPreview = mFolderIcon.getItemsToDisplay(); + + // Match size/scale of icons in the preview + float previewScale = rule.scaleForItem(0, itemsInPreview.size()); + float previewSize = rule.getIconSize() * previewScale; + float folderScale = previewSize / itemsInPreview.get(0).getIconSize(); + + final float initialScale = folderScale; + final float finalScale = 1f; + float scale = isOpening ? initialScale : finalScale; + mFolder.setScaleX(scale); + mFolder.setScaleY(scale); + + // Match position of the FolderIcon + final Rect folderIconPos = new Rect(); + float scaleRelativeToDragLayer = mLauncher.getDragLayer() + .getDescendantRectRelativeToSelf(mFolderIcon, folderIconPos); + folderScale *= scaleRelativeToDragLayer; + + // We want to create a small X offset for the preview items, so that they follow their + // expected path to their final locations. ie. an icon should not move right, if it's final + // location is to its left. This value is arbitrarily defined. + final int nudgeOffsetX = (int) (previewSize / 2); + + final int paddingOffsetX = (int) ((mFolder.getPaddingLeft() + mContent.getPaddingLeft()) + * folderScale); + final int paddingOffsetY = (int) ((mFolder.getPaddingTop() + mContent.getPaddingTop()) + * folderScale); + + int initialX = folderIconPos.left + mFolderIcon.mBackground.getOffsetX() - paddingOffsetX + - nudgeOffsetX; + int initialY = folderIconPos.top + mFolderIcon.mBackground.getOffsetY() - paddingOffsetY; + final float xDistance = initialX - lp.x; + final float yDistance = initialY - lp.y; + + // Set up the Folder background. + final int finalColor = Themes.getAttrColor(mContext, android.R.attr.colorPrimary); + final int initialColor = + ColorUtils.setAlphaComponent(finalColor, mPreviewBackground.getBackgroundAlpha()); + mFolderBackground.setColor(isOpening ? initialColor : finalColor); + + // Initialize the Folder items' text. + final List<BubbleTextView> itemsOnCurrentPage = mFolder.getItemsOnCurrentPage(); + final int finalTextColor = Themes.getAttrColor(mContext, android.R.attr.textColorSecondary); + ITEMS_TEXT_COLOR_PROPERTY.set(itemsOnCurrentPage, isOpening ? Color.TRANSPARENT + : finalTextColor); + + // Create the animators. + AnimatorSet a = LauncherAnimUtils.createAnimatorSet(); + a.setDuration(mFolder.mMaterialExpandDuration); + + ObjectAnimator translationX = isOpening + ? ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_X, xDistance, 0) + : ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_X, 0, xDistance); + a.play(translationX); + + ObjectAnimator translationY = isOpening + ? ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_Y, yDistance, 0) + : ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_Y, 0, yDistance); + a.play(translationY); + + ObjectAnimator scaleAnimator = isOpening + ? ObjectAnimator.ofFloat(mFolder, SCALE_PROPERTY, initialScale, finalScale) + : ObjectAnimator.ofFloat(mFolder, SCALE_PROPERTY, finalScale, initialScale); + a.play(scaleAnimator); + + ObjectAnimator itemsTextColor = isOpening + ? ObjectAnimator.ofArgb(itemsOnCurrentPage, ITEMS_TEXT_COLOR_PROPERTY, + Color.TRANSPARENT, finalTextColor) + : ObjectAnimator.ofArgb(itemsOnCurrentPage, ITEMS_TEXT_COLOR_PROPERTY, + finalTextColor, Color.TRANSPARENT); + a.play(itemsTextColor); + + ObjectAnimator backgroundColor = isOpening + ? ObjectAnimator.ofArgb(mFolderBackground, "color", initialColor, finalColor) + : ObjectAnimator.ofArgb(mFolderBackground, "color", finalColor, initialColor); + a.play(backgroundColor); + + // Set up the reveal animation that clips the Folder. + float stroke = mPreviewBackground.getStrokeWidth(); + int initialSize = (int) ((mFolderIcon.mBackground.getRadius() * 2 + stroke) / folderScale); + int totalOffsetX = paddingOffsetX + Math.round(nudgeOffsetX / folderScale); + int unscaledStroke = (int) Math.floor(stroke / folderScale); + Rect startRect = new Rect(totalOffsetX + unscaledStroke, unscaledStroke, + totalOffsetX + initialSize, initialSize); + Rect endRect = new Rect(0, 0, lp.width, lp.height); + a.play(getRevealAnimator(isOpening, initialSize / 2f, startRect, endRect)); + + a.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + ITEMS_TEXT_COLOR_PROPERTY.set(itemsOnCurrentPage, finalTextColor); + } + }); + + // We set the interpolator on all current child animators here, because the preview item + // animators may use a different interpolator. + for (Animator animator : a.getChildAnimations()) { + animator.setInterpolator(isOpening ? mOpeningInterpolator : mClosingInterpolator); + } + + addPreviewItemAnimatorsToSet(a, isOpening, folderScale, nudgeOffsetX); + return a; + } + + private Animator getRevealAnimator(boolean isOpening, float circleRadius, Rect start, + Rect end) { + boolean revealIsRunning = mRevealAnimator != null && mRevealAnimator.isRunning(); + final float finalRadius = revealIsRunning + ? ((RoundedRectRevealOutlineProvider) mFolder.getOutlineProvider()).getRadius() + : Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics()); + if (revealIsRunning) { + mRevealAnimator.cancel(); + } + mRevealAnimator = new RoundedRectRevealOutlineProvider(circleRadius, finalRadius, + start, end).createRevealAnimator(mFolder, !isOpening); + return mRevealAnimator; + } + + /** + * Animate the items that are displayed in the preview. + */ + private void addPreviewItemAnimatorsToSet(AnimatorSet animatorSet, boolean isOpening, + final float folderScale, int nudgeOffsetX) { + FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule(); + final List<BubbleTextView> itemsInPreview = mFolderIcon.getItemsToDisplay(); + final int numItemsInPreview = itemsInPreview.size(); + + TimeInterpolator previewItemInterpolator = getPreviewItemInterpolator(isOpening); + + ShortcutAndWidgetContainer cwc = mContent.getPageAt(0).getShortcutsAndWidgets(); + for (int i = 0; i < numItemsInPreview; ++i) { + final BubbleTextView btv = itemsInPreview.get(i); + CellLayout.LayoutParams btvLp = (CellLayout.LayoutParams) btv.getLayoutParams(); + + // Calculate the final values in the LayoutParams. + btvLp.isLockedToGrid = true; + cwc.setupLp(btv); + + // Match scale of icons in the preview. + float previewScale = rule.scaleForItem(i, numItemsInPreview); + float previewSize = rule.getIconSize() * previewScale; + float iconScale = previewSize / itemsInPreview.get(i).getIconSize(); + + final float initialScale = iconScale / folderScale; + final float finalScale = 1f; + float scale = isOpening ? initialScale : finalScale; + btv.setScaleX(scale); + btv.setScaleY(scale); + + // Match positions of the icons in the folder with their positions in the preview + rule.computePreviewItemDrawingParams(i, numItemsInPreview, mTmpParams); + // The PreviewLayoutRule assumes that the icon size takes up the entire width so we + // offset by the actual size. + int iconOffsetX = (int) ((btvLp.width - btv.getIconSize()) * iconScale) / 2; + + final int previewPosX = + (int) ((mTmpParams.transX - iconOffsetX + nudgeOffsetX) / folderScale); + final int previewPosY = (int) (mTmpParams.transY / folderScale); + + final float xDistance = previewPosX - btvLp.x; + final float yDistance = previewPosY - btvLp.y; + + ObjectAnimator translationX = isOpening + ? ObjectAnimator.ofFloat(btv, View.TRANSLATION_X, xDistance, 0) + : ObjectAnimator.ofFloat(btv, View.TRANSLATION_X, 0, xDistance); + translationX.setInterpolator(previewItemInterpolator); + animatorSet.play(translationX); + + ObjectAnimator translationY = isOpening + ? ObjectAnimator.ofFloat(btv, View.TRANSLATION_Y, yDistance, 0) + : ObjectAnimator.ofFloat(btv, View.TRANSLATION_Y, 0, yDistance); + translationY.setInterpolator(previewItemInterpolator); + animatorSet.play(translationY); + + ObjectAnimator scaleAnimator = isOpening + ? ObjectAnimator.ofFloat(btv, SCALE_PROPERTY, initialScale, finalScale) + : ObjectAnimator.ofFloat(btv, SCALE_PROPERTY, finalScale, initialScale); + scaleAnimator.setInterpolator(previewItemInterpolator); + animatorSet.play(scaleAnimator); + + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + btv.setTranslationX(0.0f); + btv.setTranslationY(0.0f); + btv.setScaleX(1f); + btv.setScaleY(1f); + } + }); + } + } + + private TimeInterpolator getPreviewItemInterpolator(boolean isOpening) { + if (mFolder.getItemCount() > FolderIcon.NUM_ITEMS_IN_PREVIEW) { + // With larger folders, we want the preview items to reach their final positions faster + // (when opening) and later (when closing) so that they appear aligned with the rest of + // the folder items when they are both visible. + return isOpening ? mPreviewItemOpeningInterpolator : mPreviewItemClosingInterpolator; + } + return isOpening ? mOpeningInterpolator : mClosingInterpolator; + } +} |