diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index cb6493c926..930cdc538f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -29,6 +29,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.content.ComponentName; import android.content.Context; import android.graphics.Rect; import android.graphics.RectF; @@ -51,6 +52,7 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; +import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.views.LauncherLayoutListener; @@ -108,17 +110,41 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @NonNull @Override public HomeAnimationFactory prepareHomeUI(Launcher activity) { - DeviceProfile dp = activity.getDeviceProfile(); + final DeviceProfile dp = activity.getDeviceProfile(); + final RecentsView recentsView = activity.getOverviewPanel(); + final ComponentName component = recentsView.getRunningTaskView().getTask().key + .sourceComponent; + + final View workspaceView = activity.getWorkspace().getFirstMatchForAppClose(component); + final FloatingIconView floatingView = workspaceView == null ? null + : new FloatingIconView(activity); + final Rect iconLocation = new Rect(); + if (floatingView != null) { + floatingView.matchPositionOf(activity, workspaceView, true /* hideOriginal */, + iconLocation); + } return new HomeAnimationFactory() { + @Nullable + @Override + public View getFloatingView() { + return floatingView; + } + @NonNull @Override public RectF getWindowTargetRect() { - int halfIconSize = dp.iconSizePx / 2; - float targetCenterX = dp.availableWidthPx / 2; - float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx; - return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize, - targetCenterX + halfIconSize, targetCenterY + halfIconSize); + final int halfIconSize = dp.iconSizePx / 2; + final float targetCenterX = dp.availableWidthPx / 2f; + final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx; + + if (workspaceView != null) { + return new RectF(iconLocation); + } else { + // Fallback to animate to center of screen. + return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize, + targetCenterX + halfIconSize, targetCenterY + halfIconSize); + } } @NonNull diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index ed83ed62fa..ab5f479c97 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -59,10 +59,10 @@ import com.android.launcher3.InsettableFrameLayout.LayoutParams; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.dragndrop.DragLayer; -import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; +import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.RecentsModel; import com.android.quickstep.util.MultiValueUpdateListener; import com.android.quickstep.util.RemoteAnimationProvider; @@ -134,7 +134,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans private final float mClosingWindowTransY; private DeviceProfile mDeviceProfile; - private View mFloatingView; + private FloatingIconView mFloatingView; private RemoteAnimationProvider mRemoteAnimationProvider; @@ -404,7 +404,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans boolean toggleVisibility) { final boolean isBubbleTextView = v instanceof BubbleTextView; if (mFloatingView == null) { - mFloatingView = new View(mLauncher); + mFloatingView = new FloatingIconView(mLauncher); } else { mFloatingView.setTranslationX(0); mFloatingView.setTranslationY(0); @@ -413,58 +413,15 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans mFloatingView.setAlpha(1); mFloatingView.setBackground(null); } - if (isBubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) { - // Create a copy of the app icon - mFloatingView.setBackground(DrawableFactory.INSTANCE.get(mLauncher) - .newIcon(v.getContext(), (ItemInfoWithIcon) v.getTag())); - } - - // Position the floating view exactly on top of the original Rect rect = new Rect(); - final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView; - if (fromDeepShortcutView) { - // Deep shortcut views have their icon drawn in a separate view. - DeepShortcutView view = (DeepShortcutView) v.getParent(); - mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect); - } else { - mDragLayer.getDescendantRectRelativeToSelf(v, rect); - } - int viewLocationLeft = rect.left; - int viewLocationTop = rect.top; + mFloatingView.matchPositionOf(mLauncher, v, toggleVisibility, rect); - float startScale = 1f; - if (isBubbleTextView && !fromDeepShortcutView) { - BubbleTextView btv = (BubbleTextView) v; - btv.getIconBounds(rect); - Drawable dr = btv.getIcon(); - if (dr instanceof FastBitmapDrawable) { - startScale = ((FastBitmapDrawable) dr).getAnimatedScale(); - } - } else { - rect.set(0, 0, rect.width(), rect.height()); - } - viewLocationLeft += rect.left; - viewLocationTop += rect.top; - int viewLocationStart = mIsRtl - ? windowTargetBounds.width() - rect.right - : viewLocationLeft; - LayoutParams lp = new LayoutParams(rect.width(), rect.height()); - lp.ignoreInsets = true; - lp.leftMargin = viewLocationStart; - lp.topMargin = viewLocationTop; + int viewLocationStart = mIsRtl ? windowTargetBounds.width() - rect.right : rect.left; + LayoutParams lp = (LayoutParams) mFloatingView.getLayoutParams(); + // Special RTL logic is needed to handle the window target bounds. + lp.leftMargin = mIsRtl ? windowTargetBounds.width() - rect.right : rect.left; mFloatingView.setLayoutParams(lp); - // Set the properties here already to make sure they'are available when running the first - // animation frame. - mFloatingView.layout(viewLocationLeft, viewLocationTop, - viewLocationLeft + rect.width(), viewLocationTop + rect.height()); - - // Swap the two views in place. - ((ViewGroup) mDragLayer.getParent()).getOverlay().add(mFloatingView); - if (toggleVisibility) { - v.setVisibility(View.INVISIBLE); - } - int[] dragLayerBounds = new int[2]; mDragLayer.getLocationOnScreen(dragLayerBounds); @@ -475,8 +432,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans float xPosition = mIsRtl ? windowTargetBounds.width() - lp.getMarginStart() - rect.width() : lp.getMarginStart(); - float dX = centerX - xPosition - (lp.width / 2); - float dY = centerY - lp.topMargin - (lp.height / 2); + float dX = centerX - xPosition - (lp.width / 2f); + float dY = centerY - lp.topMargin - (lp.height / 2f); ObjectAnimator x = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_X, 0f, dX); ObjectAnimator y = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_Y, 0f, dY); @@ -502,6 +459,14 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans float maxScaleX = windowTargetBounds.width() / (float) rect.width(); float maxScaleY = windowTargetBounds.height() / (float) rect.height(); float scale = Math.max(maxScaleX, maxScaleY); + float startScale = 1f; + if (isBubbleTextView && !(v.getParent() instanceof DeepShortcutView)) { + Drawable dr = ((BubbleTextView) v).getIcon(); + if (dr instanceof FastBitmapDrawable) { + startScale = ((FastBitmapDrawable) dr).getAnimatedScale(); + } + } + ObjectAnimator scaleAnim = ObjectAnimator .ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale); scaleAnim.setDuration(APP_LAUNCH_DURATION) @@ -521,6 +486,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans alpha.setInterpolator(LINEAR); appOpenAnimator.play(alpha); + appOpenAnimator.addListener(mFloatingView); appOpenAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index 0bcc154dfa..bb64c2bac5 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -151,6 +151,11 @@ public interface ActivityControlHelper { interface HomeAnimationFactory { + /** Return the floating view that will animate in sync with the closing window. */ + default @Nullable View getFloatingView() { + return null; + } + @NonNull RectF getWindowTargetRect(); @NonNull Animator createActivityAnimationToHome(); diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index a3363cda20..aeb648dfe2 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -82,9 +82,11 @@ import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.util.RaceConditionTracker; import com.android.launcher3.util.TraceHelper; +import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.ActivityControlHelper.ActivityInitListener; import com.android.quickstep.ActivityControlHelper.AnimationFactory; import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState; +import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory; import com.android.quickstep.ActivityControlHelper.LayoutListener; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.RemoteAnimationTargetSet; @@ -924,13 +926,13 @@ public class WindowTransformSwipeHandler private void animateToProgressInternal(float start, float end, long duration, Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) { mGestureEndTarget = target; - ActivityControlHelper.HomeAnimationFactory homeAnimFactory; + HomeAnimationFactory homeAnimFactory; Animator windowAnim; if (mGestureEndTarget == HOME) { if (mActivity != null) { homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity); } else { - homeAnimFactory = new ActivityControlHelper.HomeAnimationFactory() { + homeAnimFactory = new HomeAnimationFactory() { @NonNull @Override public RectF getWindowTargetRect() { @@ -948,7 +950,7 @@ public class WindowTransformSwipeHandler mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, isPresent -> mRecentsView.startHome()); } - windowAnim = createWindowAnimationToHome(start, homeAnimFactory.getWindowTargetRect()); + windowAnim = createWindowAnimationToHome(start, homeAnimFactory); mLauncherTransitionController = null; } else { windowAnim = mCurrentShift.animateToValue(start, end); @@ -998,20 +1000,25 @@ public class WindowTransformSwipeHandler /** * Creates an Animator that transforms the current app window into the home app. * @param startProgress The progress of {@link #mCurrentShift} to start the window from. - * @param endTarget Where to animate the window towards. + * @param homeAnimationFactory The home animation factory. */ - private Animator createWindowAnimationToHome(float startProgress, RectF endTarget) { + private Animator createWindowAnimationToHome(float startProgress, + HomeAnimationFactory homeAnimationFactory) { final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet; RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet, mTransformParams.setProgress(startProgress))); RectF originalTarget = new RectF(mClipAnimationHelper.getTargetRect()); - final RectF finalTarget = endTarget; + final RectF finalTarget = homeAnimationFactory.getWindowTargetRect(); final RectFEvaluator rectFEvaluator = new RectFEvaluator(); final RectF targetRect = new RectF(); final RectF currentRect = new RectF(); + final View floatingView = homeAnimationFactory.getFloatingView(); ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + if (floatingView instanceof FloatingIconView) { + anim.addListener((FloatingIconView) floatingView); + } anim.addUpdateListener(animation -> { float progress = animation.getAnimatedFraction(); float interpolatedProgress = Interpolators.ACCEL_2.getInterpolation(progress); @@ -1026,6 +1033,10 @@ public class WindowTransformSwipeHandler mTransformParams.setCurrentRectAndTargetAlpha(currentRect, alpha) .setSyncTransactionApplier(mSyncTransactionApplier); mClipAnimationHelper.applyTransform(targetSet, mTransformParams); + + if (floatingView instanceof FloatingIconView) { + ((FloatingIconView) floatingView).update(currentRect, 1f - alpha); + } }); anim.addListener(new AnimationSuccessListener() { @Override diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 812cf9f8e4..296c951bfd 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -26,12 +26,15 @@ import android.graphics.PointF; import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.Surface; +import android.view.View; import android.view.WindowManager; import com.android.launcher3.CellLayout.ContainerType; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.icons.IconNormalizer; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; + public class DeviceProfile { public final InvariantDeviceProfile inv; @@ -574,6 +577,33 @@ public class DeviceProfile { } } + /** + * Gets an item's location on the home screen. This is useful if the home screen + * is animating, otherwise use {@link View#getLocationOnScreen(int[])}. + * + * TODO(b/123900446): Handle landscape mode + * @param pageDiff The page difference relative to the current page. + */ + public void getItemLocation(int cellX, int cellY, int spanX, int spanY, int container, + int pageDiff, Rect outBounds) { + outBounds.setEmpty(); + outBounds.left = mInsets.left + + workspacePadding.left + cellLayoutPaddingLeftRightPx + (cellX * getCellSize().x); + outBounds.top = mInsets.top; + if (container == CONTAINER_HOTSEAT) { + outBounds.top += workspacePadding.top + + (inv.numRows * getCellSize().y) + + verticalDragHandleSizePx + - verticalDragHandleOverlapWorkspace; + outBounds.bottom = outBounds.top + hotseatBarSizePx - hotseatBarBottomPaddingPx; + } else { + outBounds.top += workspacePadding.top + (cellY * getCellSize().y); + outBounds.bottom = outBounds.top + (getCellSize().y * spanY); + outBounds.left += (pageDiff) * availableWidthPx; + } + outBounds.right = outBounds.left + (getCellSize().x * spanX); + } + private static Context getContext(Context c, int orientation) { Configuration context = new Configuration(c.getResources().getConfiguration()); context.orientation = orientation; diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 1dec1731d3..e5ab2d1298 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -52,6 +52,8 @@ import android.view.View; import android.view.animation.Interpolator; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.util.IntArray; import java.io.Closeable; @@ -65,6 +67,9 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; + /** * Various utilities shared amongst the Launcher's classes. */ @@ -541,4 +546,48 @@ public final class Utilities { public static String getPointString(int x, int y) { return String.format(Locale.ENGLISH, "%d,%d", x, y); } + + /** + * Returns the location bounds of a view. + * - For DeepShortcutView, we return the bounds of the icon view. + * - For BubbleTextView, we return the icon bounds. + */ + public static void getLocationBoundsForView(Launcher launcher, View v, Rect outRect) { + final DragLayer dragLayer = launcher.getDragLayer(); + final boolean isBubbleTextView = v instanceof BubbleTextView; + final Rect rect = new Rect(); + + final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView; + if (fromDeepShortcutView) { + // Deep shortcut views have their icon drawn in a separate view. + DeepShortcutView view = (DeepShortcutView) v.getParent(); + dragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect); + } else if (isBubbleTextView && v.getTag() instanceof ItemInfo + && (((ItemInfo) v.getTag()).container == CONTAINER_DESKTOP + || ((ItemInfo) v.getTag()).container == CONTAINER_HOTSEAT)) { + BubbleTextView btv = (BubbleTextView) v; + CellLayout pageViewIsOn = ((CellLayout) btv.getParent().getParent()); + int pageNum = launcher.getWorkspace().indexOfChild(pageViewIsOn); + + DeviceProfile dp = launcher.getDeviceProfile(); + ItemInfo info = ((ItemInfo) btv.getTag()); + dp.getItemLocation(info.cellX, info.cellY, info.spanX, info.spanY, + info.container, pageNum - launcher.getCurrentWorkspaceScreen(), rect); + } else { + dragLayer.getDescendantRectRelativeToSelf(v, rect); + } + int viewLocationLeft = rect.left; + int viewLocationTop = rect.top; + + if (isBubbleTextView && !fromDeepShortcutView) { + BubbleTextView btv = (BubbleTextView) v; + btv.getIconBounds(rect); + } else { + rect.set(0, 0, rect.width(), rect.height()); + } + viewLocationLeft += rect.left; + viewLocationTop += rect.top; + outRect.set(viewLocationLeft, viewLocationTop, viewLocationLeft + rect.width(), + viewLocationTop + rect.height()); + } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 2db6cd922d..7f5ca423da 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -34,6 +34,7 @@ import android.annotation.SuppressLint; import android.app.WallpaperManager; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; @@ -97,6 +98,7 @@ import com.android.launcher3.widget.PendingAppWidgetHostView; import java.util.ArrayList; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import java.util.function.Predicate; @@ -2889,6 +2891,88 @@ public class Workspace extends PagedView return layouts; } + /** + * Returns a list of all the CellLayouts on the Homescreen, starting with + * {@param startPage}, then going outward alternating between pages prior to the startPage, + * and then the pages after the startPage. + * ie. if there are 5 pages [0, 1, 2, 3, 4] and startPage is 1, we return [1, 0, 2, 3, 4]. + */ + private CellLayout[] getWorkspaceCellLayouts(int startPage) { + int screenCount = getChildCount(); + final CellLayout[] layouts = new CellLayout[screenCount]; + int screen = 0; + + layouts[screen] = (CellLayout) getChildAt(startPage); + screen++; + + for (int i = 1; screen < screenCount; ++i) { + CellLayout prevPage = (CellLayout) getChildAt(startPage - i); + CellLayout nextPage = (CellLayout) getChildAt(startPage + i); + + if (prevPage != null) { + layouts[screen] = prevPage; + screen++; + } + if (nextPage != null) { + layouts[screen] = nextPage; + screen++; + } + } + return layouts; + } + + /** + * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close + * animation. + * + * @param component The component of the task being dismissed. + */ + public View getFirstMatchForAppClose(ComponentName component) { + final int curPage = getCurrentPage(); + final CellLayout currentPage = (CellLayout) getPageAt(curPage); + final Workspace.ItemOperator isItemComponent = (info, view) -> + info != null && Objects.equals(info.getTargetComponent(), component); + final Workspace.ItemOperator isItemInFolder = (info, view) -> { + if (info instanceof FolderInfo) { + FolderInfo folderInfo = (FolderInfo) info; + for (ShortcutInfo shortcutInfo : folderInfo.contents) { + if (Objects.equals(shortcutInfo.getTargetComponent(), component)) { + return true; + } + } + } + return false; + }; + + CellLayout[] hotseatAndCurrentPage = new CellLayout[] { getHotseat(), currentPage }; + // First we look if the app itself is in the hotseat or on the current workspace page. + View icon = getFirstMatch(hotseatAndCurrentPage, isItemComponent); + if (icon != null) { + return icon; + } + // Then we look if the app is in a folder on the hotseat or current workspace page. + icon = getFirstMatch(hotseatAndCurrentPage, isItemInFolder); + if (icon != null) { + return icon; + } + // Continue searching for the app or for a folder with the app on other pages of the + // workspace. We skip the current page, since we already searched above. + CellLayout[] allPages = getWorkspaceCellLayouts(curPage); + CellLayout[] page = new CellLayout[1]; + for (int i = 1; i < allPages.length; ++i) { + page[0] = allPages[i]; + icon = getFirstMatch(page, isItemComponent); + if (icon != null) { + return icon; + } + icon = getFirstMatch(page, isItemInFolder); + if (icon != null) { + return icon; + } + } + return null; + } + public View getHomescreenIconByItemId(final int id) { return getFirstMatch((info, v) -> info != null && info.id == id); } @@ -2914,6 +2998,23 @@ public class Workspace extends PagedView return value[0]; } + private View getFirstMatch(CellLayout[] cellLayouts, final ItemOperator operator) { + final View[] value = new View[1]; + for (CellLayout cellLayout : cellLayouts) { + mapOverCellLayout(MAP_NO_RECURSE, cellLayout, (info, v) -> { + if (operator.evaluate(info, v)) { + value[0] = v; + return true; + } + return false; + }); + if (value[0] != null) { + break; + } + } + return value[0]; + } + void clearDropTargets() { mapOverItems(MAP_NO_RECURSE, new ItemOperator() { @Override @@ -2992,31 +3093,38 @@ public class Workspace extends PagedView */ public void mapOverItems(boolean recurse, ItemOperator op) { for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { - ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets(); - // map over all the shortcuts on the workspace - final int itemCount = container.getChildCount(); - for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { - View item = container.getChildAt(itemIdx); - ItemInfo info = (ItemInfo) item.getTag(); - if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) { - FolderIcon folder = (FolderIcon) item; - ArrayList folderChildren = folder.getFolder().getItemsInReadingOrder(); - // map over all the children in the folder - final int childCount = folderChildren.size(); - for (int childIdx = 0; childIdx < childCount; childIdx++) { - View child = folderChildren.get(childIdx); - info = (ItemInfo) child.getTag(); - if (op.evaluate(info, child)) { - return; - } - } - } else { - if (op.evaluate(info, item)) { - return; + if (mapOverCellLayout(recurse, layout, op)) { + return; + } + } + } + + private boolean mapOverCellLayout(boolean recurse, CellLayout layout, ItemOperator op) { + ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets(); + // map over all the shortcuts on the workspace + final int itemCount = container.getChildCount(); + for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { + View item = container.getChildAt(itemIdx); + ItemInfo info = (ItemInfo) item.getTag(); + if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) { + FolderIcon folder = (FolderIcon) item; + ArrayList folderChildren = folder.getFolder().getItemsInReadingOrder(); + // map over all the children in the folder + final int childCount = folderChildren.size(); + for (int childIdx = 0; childIdx < childCount; childIdx++) { + View child = folderChildren.get(childIdx); + info = (ItemInfo) child.getTag(); + if (op.evaluate(info, child)) { + return true; } } + } else { + if (op.evaluate(info, item)) { + return true; + } } } + return false; } void updateShortcuts(ArrayList shortcuts) { diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java new file mode 100644 index 0000000000..07318c91ba --- /dev/null +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2019 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.views; + +import android.animation.Animator; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.View; +import android.view.ViewGroup; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.InsettableFrameLayout.LayoutParams; +import com.android.launcher3.ItemInfoWithIcon; +import com.android.launcher3.Launcher; +import com.android.launcher3.Utilities; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.graphics.DrawableFactory; + +/** + * A view that is created to look like another view with the purpose of creating fluid animations. + */ +public class FloatingIconView extends View implements Animator.AnimatorListener { + + private Runnable mStartRunnable; + private Runnable mEndRunnable; + + public FloatingIconView(Context context) { + super(context); + } + + public void setRunnables(Runnable startRunnable, Runnable endRunnable) { + mStartRunnable = startRunnable; + mEndRunnable = endRunnable; + } + + /** + * Positions this view to match the size and location of {@param rect}. + */ + public void update(RectF rect, float alpha) { + setAlpha(alpha); + + LayoutParams lp = (LayoutParams) getLayoutParams(); + float dX = rect.left - lp.leftMargin; + float dY = rect.top - lp.topMargin; + setTranslationX(dX); + setTranslationY(dY); + + float scaleX = rect.width() / (float) getWidth(); + float scaleY = rect.height() / (float) getHeight(); + float scale = Math.min(scaleX, scaleY); + setPivotX(0); + setPivotY(0); + setScaleX(scale); + setScaleY(scale); + } + + @Override + public void onAnimationStart(Animator animator) { + if (mStartRunnable != null) { + mStartRunnable.run(); + } + } + + @Override + public void onAnimationEnd(Animator animator) { + if (mEndRunnable != null) { + mEndRunnable.run(); + } + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + + /** + * Sets the size and position of this view to match {@param v}. + * + * @param v The view to copy + * @param hideOriginal If true, it will hide {@param v} while this view is visible. + * @param positionOut Rect that will hold the size and position of v. + */ + public void matchPositionOf(Launcher launcher, View v, boolean hideOriginal, Rect positionOut) { + Utilities.getLocationBoundsForView(launcher, v, positionOut); + final LayoutParams lp = new LayoutParams(positionOut.width(), positionOut.height()); + lp.ignoreInsets = true; + + // Position the floating view exactly on top of the original + lp.leftMargin = positionOut.left; + lp.topMargin = positionOut.top; + setLayoutParams(lp); + // Set the properties here already to make sure they are available when running the first + // animation frame. + layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin + + lp.height); + + if (v instanceof BubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) { + // Create a copy of the app icon + setBackground(DrawableFactory.INSTANCE.get(launcher) + .newIcon(v.getContext(), (ItemInfoWithIcon) v.getTag())); + } + + // We need to add it to the overlay, but keep it invisible until animation starts.. + final DragLayer dragLayer = launcher.getDragLayer(); + setVisibility(INVISIBLE); + ((ViewGroup) dragLayer.getParent()).getOverlay().add(this); + + setRunnables(() -> { + setVisibility(VISIBLE); + if (hideOriginal) { + v.setVisibility(INVISIBLE); + } + }, + () -> { + ((ViewGroup) dragLayer.getParent()).getOverlay().remove(this); + if (hideOriginal) { + v.setVisibility(VISIBLE); + } + }); + } +}