diff options
Diffstat (limited to 'src/com/android/launcher3/folder')
7 files changed, 608 insertions, 114 deletions
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java index 840fcf5fe..503c2ec9f 100644 --- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java @@ -121,6 +121,11 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule } @Override + public float getIconSize() { + return mIconSize; + } + + @Override public int maxNumItems() { return MAX_NUM_ITEMS_IN_PREVIEW; } @@ -129,24 +134,4 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule public boolean clipToBackground() { return true; } - - @Override - public List<View> getItemsToDisplay(Folder folder) { - List<View> items = new ArrayList<>(folder.getItemsInReadingOrder()); - int numItems = items.size(); - if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION && numItems > MAX_NUM_ITEMS_IN_PREVIEW) { - // We match the icons in the preview with the layout of the opened folder (b/27944225), - // but we still need to figure out how we want to handle updating the preview when the - // upper left quadrant changes. - int appsPerRow = folder.mContent.getPageAt(0).getCountX(); - int appsToDelete = appsPerRow - MAX_NUM_ITEMS_PER_ROW; - - // We only display the upper left quadrant. - while (appsToDelete > 0) { - items.remove(MAX_NUM_ITEMS_PER_ROW); - appsToDelete--; - } - } - return items.subList(0, Math.min(numItems, MAX_NUM_ITEMS_IN_PREVIEW)); - } } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 3d2ffb4ff..bce13bc40 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -46,6 +46,7 @@ import android.widget.TextView; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Alarm; import com.android.launcher3.AppInfo; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; @@ -66,8 +67,8 @@ import com.android.launcher3.UninstallDropTarget.DropTargetSource; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; +import com.android.launcher3.anim.AnimationLayerSet; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragLayer; @@ -75,12 +76,13 @@ import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; -import com.android.launcher3.util.CircleRevealOutlineProvider; +import com.android.launcher3.anim.CircleRevealOutlineProvider; import com.android.launcher3.util.Thunk; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.List; /** * Represents a set of icons chosen by the user or generated by the system. @@ -133,8 +135,10 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC @Thunk final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>(); + private AnimatorSet mCurrentAnimator; + private final int mExpandDuration; - private final int mMaterialExpandDuration; + public final int mMaterialExpandDuration; private final int mMaterialExpandStagger; protected final Launcher mLauncher; @@ -510,51 +514,35 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC mState = STATE_SMALL; } - /** - * Opens the user folder described by the specified tag. The opening of the folder - * is animated relative to the specified View. If the View is null, no animation - * is played. - */ - public void animateOpen() { - Folder openFolder = getOpen(mLauncher); - if (openFolder != null && openFolder != this) { - // Close any open folder before opening a folder. - openFolder.close(true); + private void startAnimation(final AnimatorSet a) { + long startTime = 0; + if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { + // This allows a nice transition when closing a Folder while it is still animating open. + startTime = mCurrentAnimator.getDuration() - mCurrentAnimator.getCurrentPlayTime(); + mCurrentAnimator.cancel(); } - - DragLayer dragLayer = mLauncher.getDragLayer(); - // Just verify that the folder hasn't already been added to the DragLayer. - // There was a one-off crash where the folder had a parent already. - if (getParent() == null) { - dragLayer.addView(this); - mDragController.addDropTarget(this); - } else { - if (ProviderConfig.IS_DOGFOOD_BUILD) { - Log.e(TAG, "Opening folder (" + this + ") which already has a parent:" - + getParent()); + a.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mState = STATE_ANIMATING; + mCurrentAnimator = a; } - } - - mIsOpen = true; - mContent.completePendingPageChanges(); - if (!mDragInProgress) { - // Open on the first page. - mContent.snapToPageImmediately(0); - } - - // This is set to true in close(), but isn't reset to false until onDropCompleted(). This - // leads to an inconsistent state if you drag out of the folder and drag back in without - // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice. - mDeleteFolderOnDropCompleted = false; + @Override + public void onAnimationEnd(Animator animation) { + mCurrentAnimator = null; + } + }); + a.setCurrentPlayTime(startTime); + a.start(); + } - final Runnable onCompleteRunnable; + private AnimatorSet getOpeningAnimator() { prepareReveal(); - centerAboutIcon(); - mFolderIcon.growAndFadeOut(); AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); + int width = getFolderWidth(); int height = getFolderHeight(); @@ -596,24 +584,75 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC anim.play(textAlpha); anim.play(reveal); - mContent.setLayerType(LAYER_TYPE_HARDWARE, null); - mFooter.setLayerType(LAYER_TYPE_HARDWARE, null); + AnimationLayerSet layerSet = new AnimationLayerSet(); + layerSet.addView(mContent); + layerSet.addView(mFooter); + anim.addListener(layerSet); + + return anim; + } + + /** + * Opens the user folder described by the specified tag. The opening of the folder + * is animated relative to the specified View. If the View is null, no animation + * is played. + */ + public void animateOpen() { + Folder openFolder = getOpen(mLauncher); + if (openFolder != null && openFolder != this) { + // Close any open folder before opening a folder. + openFolder.close(true); + } + + DragLayer dragLayer = mLauncher.getDragLayer(); + // Just verify that the folder hasn't already been added to the DragLayer. + // There was a one-off crash where the folder had a parent already. + if (getParent() == null) { + dragLayer.addView(this); + mDragController.addDropTarget(this); + } else { + if (FeatureFlags.IS_DOGFOOD_BUILD) { + Log.e(TAG, "Opening folder (" + this + ") which already has a parent:" + + getParent()); + } + } + + mIsOpen = true; + + mContent.completePendingPageChanges(); + if (!mDragInProgress) { + // Open on the first page. + mContent.snapToPageImmediately(0); + } + + // This is set to true in close(), but isn't reset to false until onDropCompleted(). This + // leads to an inconsistent state if you drag out of the folder and drag back in without + // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice. + mDeleteFolderOnDropCompleted = false; + + final Runnable onCompleteRunnable; + centerAboutIcon(); + + AnimatorSet anim = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION + ? new FolderAnimationManager(this, true /* isOpening */).getAnimator() + : getOpeningAnimator(); onCompleteRunnable = new Runnable() { @Override public void run() { - mContent.setLayerType(LAYER_TYPE_NONE, null); - mFooter.setLayerType(LAYER_TYPE_NONE, null); mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); } }; anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { + if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { + mFolderIcon.setVisibility(INVISIBLE); + } + Utilities.sendCustomAccessibilityEvent( Folder.this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, mContent.getAccessibilityDescription()); - mState = STATE_ANIMATING; } @Override public void onAnimationEnd(Animator animation) { @@ -659,7 +698,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } mPageIndicator.stopAllAnimations(); - anim.start(); + startAnimation(anim); // Make sure the folder picks up the last drag move even if the finger doesn't move. if (mDragController.isDragging()) { @@ -697,7 +736,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC mFolderName.dispatchBackKey(); } - if (mFolderIcon != null) { + if (mFolderIcon != null && !FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { mFolderIcon.shrinkAndFadeIn(animate); } @@ -715,12 +754,24 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC parent.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } + private AnimatorSet getClosingAnimator() { + AnimatorSet animatorSet = LauncherAnimUtils.createAnimatorSet(); + animatorSet.play(LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f)); + + AnimationLayerSet layerSet = new AnimationLayerSet(); + layerSet.addView(this); + animatorSet.addListener(layerSet); + animatorSet.setDuration(mExpandDuration); + return animatorSet; + } + private void animateClosed() { - final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f); - oa.addListener(new AnimatorListenerAdapter() { + AnimatorSet a = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION + ? new FolderAnimationManager(this, false /* isOpening */).getAnimator() + : getClosingAnimator(); + a.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - setLayerType(LAYER_TYPE_NONE, null); closeComplete(true); } @Override @@ -729,12 +780,9 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC Folder.this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, getContext().getString(R.string.folder_closed)); - mState = STATE_ANIMATING; } }); - oa.setDuration(mExpandDuration); - setLayerType(LAYER_TYPE_HARDWARE, null); - oa.start(); + startAnimation(a); } private void closeComplete(boolean wasAnimated) { @@ -745,10 +793,14 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } mDragController.removeDropTarget(this); clearFocus(); - if (wasAnimated) { - mFolderIcon.requestFocus(); + if (mFolderIcon != null) { + mFolderIcon.setVisibility(View.VISIBLE); + if (wasAnimated) { + mFolderIcon.requestFocus(); + } } + if (mRearrangeOnClose) { rearrangeChildren(); mRearrangeOnClose = false; @@ -1436,6 +1488,26 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC return mItemsInReadingOrder; } + public List<BubbleTextView> getItemsOnCurrentPage() { + ArrayList<View> allItems = getItemsInReadingOrder(); + int currentPage = mContent.getCurrentPage(); + int lastPage = mContent.getPageCount() - 1; + int totalItemsInFolder = allItems.size(); + int itemsPerPage = mContent.itemsPerPage(); + int numItemsOnCurrentPage = currentPage == lastPage + ? totalItemsInFolder - (itemsPerPage * currentPage) + : itemsPerPage; + + int startIndex = currentPage * itemsPerPage; + int endIndex = startIndex + numItemsOnCurrentPage; + + List<BubbleTextView> itemsOnCurrentPage = new ArrayList<>(numItemsOnCurrentPage); + for (int i = startIndex; i < endIndex; ++i) { + itemsOnCurrentPage.add((BubbleTextView) allItems.get(i)); + } + return itemsOnCurrentPage; + } + public void onFocusChange(View v, boolean hasFocus) { if (v == mFolderName) { if (hasFocus) { diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java new file mode 100644 index 000000000..c1c974ca3 --- /dev/null +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -0,0 +1,325 @@ +/* + * 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 final boolean mIsOpening; + + 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, boolean isOpening) { + mFolder = folder; + mContent = folder.mContent; + mFolderBackground = (GradientDrawable) mFolder.getBackground(); + + mFolderIcon = folder.mFolderIcon; + mPreviewBackground = mFolderIcon.mBackground; + + mContext = folder.getContext(); + mLauncher = folder.mLauncher; + + mIsOpening = isOpening; + + 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); + } + + + /** + * Prepares the Folder for animating between open / closed states. + */ + public AnimatorSet getAnimator() { + 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 = mIsOpening ? initialScale : finalScale; + mFolder.setScaleX(scale); + mFolder.setScaleY(scale); + mFolder.setPivotX(0); + mFolder.setPivotY(0); + + // 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(mIsOpening ? initialColor : finalColor); + + // Initialize the Folder items' text. + final List<BubbleTextView> items = mFolder.getItemsOnCurrentPage(); + final int finalTextColor = Themes.getAttrColor(mContext, android.R.attr.textColorSecondary); + ITEMS_TEXT_COLOR_PROPERTY.set(items, mIsOpening ? Color.TRANSPARENT + : finalTextColor); + + // 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); + float finalRadius = Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics()); + + // Create the animators. + AnimatorSet a = LauncherAnimUtils.createAnimatorSet(); + a.setDuration(mFolder.mMaterialExpandDuration); + + a.play(getAnimator(mFolder, View.TRANSLATION_X, xDistance, 0f)); + a.play(getAnimator(mFolder, View.TRANSLATION_Y, yDistance, 0f)); + a.play(getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale)); + a.play(getAnimator(items, ITEMS_TEXT_COLOR_PROPERTY, Color.TRANSPARENT, finalTextColor)); + a.play(getAnimator(mFolderBackground, "color", initialColor, finalColor)); + a.play(new RoundedRectRevealOutlineProvider(initialSize / 2f, finalRadius, startRect, + endRect).createRevealAnimator(mFolder, !mIsOpening)); + + a.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + ITEMS_TEXT_COLOR_PROPERTY.set(items, finalTextColor); + mFolder.setTranslationX(0.0f); + mFolder.setTranslationY(0.0f); + mFolder.setScaleX(1f); + mFolder.setScaleY(1f); + } + }); + + // 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(mIsOpening ? mOpeningInterpolator : mClosingInterpolator); + } + + addPreviewItemAnimatorsToSet(a, folderScale, nudgeOffsetX); + return a; + } + + /** + * Animate the items that are displayed in the preview. + */ + private void addPreviewItemAnimatorsToSet(AnimatorSet animatorSet, final float folderScale, + int nudgeOffsetX) { + FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule(); + final List<BubbleTextView> itemsInPreview = mFolderIcon.getItemsToDisplay(); + final int numItemsInPreview = itemsInPreview.size(); + + TimeInterpolator previewItemInterpolator = getPreviewItemInterpolator(); + + 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 = mIsOpening ? 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; + + Animator translationX = getAnimator(btv, View.TRANSLATION_X, xDistance, 0f); + translationX.setInterpolator(previewItemInterpolator); + animatorSet.play(translationX); + + Animator translationY = getAnimator(btv, View.TRANSLATION_Y, yDistance, 0f); + translationY.setInterpolator(previewItemInterpolator); + animatorSet.play(translationY); + + Animator scaleAnimator = getAnimator(btv, SCALE_PROPERTY, initialScale, finalScale); + 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() { + 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 mIsOpening ? mPreviewItemOpeningInterpolator : mPreviewItemClosingInterpolator; + } + return mIsOpening ? mOpeningInterpolator : mClosingInterpolator; + } + + private Animator getAnimator(View view, Property property, float v1, float v2) { + return mIsOpening + ? ObjectAnimator.ofFloat(view, property, v1, v2) + : ObjectAnimator.ofFloat(view, property, v2, v1); + } + + private Animator getAnimator(List<BubbleTextView> items, Property property, int v1, int v2) { + return mIsOpening + ? ObjectAnimator.ofArgb(items, property, v1, v2) + : ObjectAnimator.ofArgb(items, property, v2, v1); + } + + private Animator getAnimator(GradientDrawable drawable, String property, int v1, int v2) { + return mIsOpening + ? ObjectAnimator.ofArgb(drawable, property, v1, v2) + : ObjectAnimator.ofArgb(drawable, property, v2, v1); + } +} diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index f21601092..ced9c9e8d 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -125,6 +125,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private float mSlop; + FolderIconPreviewVerifier mPreviewVerifier; private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0); private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<PreviewItemDrawingParams>(); private Drawable mReferenceDrawable = null; @@ -179,7 +180,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { } DeviceProfile grid = launcher.getDeviceProfile(); - FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false); + FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext()) + .inflate(resId, group, false); icon.setClipToPadding(false); icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name); @@ -219,6 +221,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void setFolder(Folder folder) { mFolder = folder; + mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv); updateItemDrawingParams(false); } @@ -405,6 +408,10 @@ public class FolderIcon extends FrameLayout implements FolderListener { mBadgeInfo = badgeInfo; } + public PreviewLayoutRule getLayoutRule() { + return mPreviewLayoutRule; + } + /** * Sets mBadgeScale to 1 or 0, animating if oldCount or newCount is 0 * (the badge is being added or removed). @@ -822,6 +829,14 @@ public class FolderIcon extends FrameLayout implements FolderListener { }; animateScale(1f, 1f, onStart, onEnd); } + + public int getBackgroundAlpha() { + return (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier); + } + + public float getStrokeWidth() { + return mStrokeWidth; + } } public void setFolderBackground(PreviewBackground bg) { @@ -989,8 +1004,26 @@ public class FolderIcon extends FrameLayout implements FolderListener { return mFolderName.getVisibility() == VISIBLE; } + public List<BubbleTextView> getItemsToDisplay() { + mPreviewVerifier.setFolderInfo(mFolder.getInfo()); + + List<BubbleTextView> itemsToDisplay = new ArrayList<>(); + List<View> allItems = mFolder.getItemsInReadingOrder(); + int numItems = allItems.size(); + for (int rank = 0; rank < numItems; ++rank) { + if (mPreviewVerifier.isItemInPreview(rank)) { + itemsToDisplay.add((BubbleTextView) allItems.get(rank)); + } + + if (itemsToDisplay.size() == FolderIcon.NUM_ITEMS_IN_PREVIEW) { + break; + } + } + return itemsToDisplay; + } + private void updateItemDrawingParams(boolean animate) { - List<View> items = mPreviewLayoutRule.getItemsToDisplay(mFolder); + List<BubbleTextView> items = getItemsToDisplay(); int nItemsInPreview = items.size(); int prevNumItems = mDrawingParams.size(); @@ -1005,7 +1038,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { for (int i = 0; i < mDrawingParams.size(); i++) { PreviewItemDrawingParams p = mDrawingParams.get(i); - p.drawable = ((TextView) items.get(i)).getCompoundDrawables()[1]; + p.drawable = items.get(i).getCompoundDrawables()[1]; if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) { computePreviewItemDrawingParams(i, nItemsInPreview, p); @@ -1174,8 +1207,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { PreviewItemDrawingParams params); void init(int availableSpace, int intrinsicIconSize, boolean rtl); float scaleForItem(int index, int totalNumItems); + float getIconSize(); int maxNumItems(); boolean clipToBackground(); - List<View> getItemsToDisplay(Folder folder); } } diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java new file mode 100644 index 000000000..de962b021 --- /dev/null +++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java @@ -0,0 +1,64 @@ +/* + * 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 com.android.launcher3.FolderInfo; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.config.FeatureFlags; + +/** + * Verifies whether an item in a Folder is displayed in the FolderIcon preview. + */ +public class FolderIconPreviewVerifier { + + private final int mMaxGridCountX; + private final int mMaxGridCountY; + private final int mMaxItemsPerPage; + private final int[] mGridSize = new int[2]; + + private int mGridCountX; + private boolean mDisplayingUpperLeftQuadrant = false; + + public FolderIconPreviewVerifier(InvariantDeviceProfile profile) { + mMaxGridCountX = profile.numFolderColumns; + mMaxGridCountY = profile.numFolderRows; + mMaxItemsPerPage = mMaxGridCountX * mMaxGridCountY; + } + + public void setFolderInfo(FolderInfo info) { + int numItemsInFolder = info.contents.size(); + mDisplayingUpperLeftQuadrant = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION + && !FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON + && numItemsInFolder > FolderIcon.NUM_ITEMS_IN_PREVIEW; + + if (mDisplayingUpperLeftQuadrant) { + FolderPagedView.calculateGridSize(info.contents.size(), 0, 0, mMaxGridCountX, + mMaxGridCountY, mMaxItemsPerPage, mGridSize); + mGridCountX = mGridSize[0]; + } + } + + public boolean isItemInPreview(int rank) { + if (mDisplayingUpperLeftQuadrant) { + // Returns true iff the icon is in the 2x2 upper left quadrant of the Folder. + int col = rank % mGridCountX; + int row = rank / mGridCountX; + return col < 2 && row < 2; + } + return rank < FolderIcon.NUM_ITEMS_IN_PREVIEW; + } +} diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index eecce183a..2a6007a4e 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -35,17 +35,14 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; -import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.pageindicators.PageIndicator; -import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -68,7 +65,7 @@ public class FolderPagedView extends PagedView { */ private static final float SCROLL_HINT_FRACTION = 0.07f; - private static final int[] sTempPosArray = new int[2]; + private static final int[] sTmpArray = new int[2]; public final boolean mIsRtl; @@ -108,7 +105,6 @@ public class FolderPagedView extends PagedView { mIsRtl = Utilities.isRtl(getResources()); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - setEdgeGlowColor(Themes.getAttrColor(context, android.R.attr.colorEdgeEffect)); mFocusIndicatorHelper = new ViewGroupFocusHelper(this); } @@ -120,40 +116,58 @@ public class FolderPagedView extends PagedView { } /** - * Sets up the grid size such that {@param count} items can fit in the grid. + * Calculates the grid size such that {@param count} items can fit in the grid. * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while * maintaining the restrictions of {@link #mMaxCountX} & {@link #mMaxCountY}. */ - private void setupContentDimensions(int count) { - mAllocatedContentSize = count; + public static void calculateGridSize(int count, int countX, int countY, int maxCountX, + int maxCountY, int maxItemsPerPage, int[] out) { boolean done; - if (count >= mMaxItemsPerPage) { - mGridCountX = mMaxCountX; - mGridCountY = mMaxCountY; + int gridCountX = countX; + int gridCountY = countY; + + if (count >= maxItemsPerPage) { + gridCountX = maxCountX; + gridCountY = maxCountY; done = true; } else { done = false; } while (!done) { - int oldCountX = mGridCountX; - int oldCountY = mGridCountY; - if (mGridCountX * mGridCountY < count) { + int oldCountX = gridCountX; + int oldCountY = gridCountY; + if (gridCountX * gridCountY < count) { // Current grid is too small, expand it - if ((mGridCountX <= mGridCountY || mGridCountY == mMaxCountY) && mGridCountX < mMaxCountX) { - mGridCountX++; - } else if (mGridCountY < mMaxCountY) { - mGridCountY++; + if ((gridCountX <= gridCountY || gridCountY == maxCountY) + && gridCountX < maxCountX) { + gridCountX++; + } else if (gridCountY < maxCountY) { + gridCountY++; } - if (mGridCountY == 0) mGridCountY++; - } else if ((mGridCountY - 1) * mGridCountX >= count && mGridCountY >= mGridCountX) { - mGridCountY = Math.max(0, mGridCountY - 1); - } else if ((mGridCountX - 1) * mGridCountY >= count) { - mGridCountX = Math.max(0, mGridCountX - 1); + if (gridCountY == 0) gridCountY++; + } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) { + gridCountY = Math.max(0, gridCountY - 1); + } else if ((gridCountX - 1) * gridCountY >= count) { + gridCountX = Math.max(0, gridCountX - 1); } - done = mGridCountX == oldCountX && mGridCountY == oldCountY; + done = gridCountX == oldCountX && gridCountY == oldCountY; } + out[0] = gridCountX; + out[1] = gridCountY; + } + + /** + * Sets up the grid size such that {@param count} items can fit in the grid. + */ + public void setupContentDimensions(int count) { + mAllocatedContentSize = count; + calculateGridSize(count, mGridCountX, mGridCountY, mMaxCountX, mMaxCountY, mMaxItemsPerPage, + sTmpArray); + mGridCountX = sTmpArray[0]; + mGridCountY = sTmpArray[1]; + // Update grid size for (int i = getPageCount() - 1; i >= 0; i--) { getPageAt(i).setGridSize(mGridCountX, mGridCountY); @@ -314,6 +328,8 @@ public class FolderPagedView extends PagedView { int position = 0; int newX, newY, rank; + FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier( + Launcher.getLauncher(getContext()).getDeviceProfile().inv); rank = 0; for (int i = 0; i < itemCount; i++) { View v = list.size() > i ? list.get(i) : null; @@ -346,7 +362,7 @@ public class FolderPagedView extends PagedView { currentPage.addViewToCellLayout( v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true); - if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) { + if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) { ((BubbleTextView) v).verifyHighRes(); } } @@ -400,12 +416,12 @@ public class FolderPagedView extends PagedView { public int findNearestArea(int pixelX, int pixelY) { int pageIndex = getNextPage(); CellLayout page = getPageAt(pageIndex); - page.findNearestArea(pixelX, pixelY, 1, 1, sTempPosArray); + page.findNearestArea(pixelX, pixelY, 1, 1, sTmpArray); if (mFolder.isLayoutRtl()) { - sTempPosArray[0] = page.getCountX() - sTempPosArray[0] - 1; + sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1; } return Math.min(mAllocatedContentSize - 1, - pageIndex * mMaxItemsPerPage + sTempPosArray[1] * mGridCountX + sTempPosArray[0]); + pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]); } public boolean isFull() { diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java index 297203ae2..9c8c2efdb 100644 --- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java @@ -87,6 +87,11 @@ public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { } @Override + public float getIconSize() { + return mBaselineIconSize; + } + + @Override public float scaleForItem(int index, int numItems) { // Scale is determined by the position of the icon in the preview. index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1; @@ -98,10 +103,4 @@ public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { public boolean clipToBackground() { return false; } - - @Override - public List<View> getItemsToDisplay(Folder folder) { - List<View> items = folder.getItemsInReadingOrder(); - return items.subList(0, Math.min(items.size(), MAX_NUM_ITEMS_IN_PREVIEW)); - } } |