Merge "App to home animation zooms into the app icon." into ub-launcher3-master
This commit is contained in:
commit
d202bf40bf
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -151,6 +151,11 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
|
|||
|
||||
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();
|
||||
|
|
|
@ -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<T extends BaseDraggingActivity>
|
|||
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<T extends BaseDraggingActivity>
|
|||
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<T extends BaseDraggingActivity>
|
|||
/**
|
||||
* 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<T extends BaseDraggingActivity>
|
|||
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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<WorkspacePageIndicator>
|
|||
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<WorkspacePageIndicator>
|
|||
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<WorkspacePageIndicator>
|
|||
*/
|
||||
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<View> 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<View> 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<ShortcutInfo> shortcuts) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue