Merge "Create return-to-home app widget animation" into sc-dev am: 92af1bfe5b

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/14332408

Change-Id: Ibe132dd330b96a8455382fb2c1e2432027e3125c
This commit is contained in:
Cyrus Boadway
2021-05-03 18:09:46 +00:00
committed by Automerger Merge Worker
6 changed files with 224 additions and 156 deletions

View File

@@ -460,7 +460,8 @@ public abstract class BaseQuickstepLauncher extends Launcher
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
// Fall through and continue if it's an app or shortcut
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
// Fall through and continue if it's an app, shortcut, or widget
break;
default:
return;

View File

@@ -60,6 +60,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.SystemProperties;
import android.util.Pair;
import android.util.Size;
import android.view.View;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -780,7 +781,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
? 0 : getWindowCornerRadius(mLauncher.getResources());
final FloatingWidgetView floatingView = FloatingWidgetView.getFloatingWidgetView(mLauncher,
v, widgetBackgroundBounds, windowTargetBounds, finalWindowRadius);
v, widgetBackgroundBounds,
new Size(windowTargetBounds.width(), windowTargetBounds.height()),
finalWindowRadius);
final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources())
? floatingView.getInitialCornerRadius() : 0;

View File

@@ -23,6 +23,7 @@ import android.util.Pair;
import android.view.View;
import android.widget.RemoteViews;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -49,6 +50,10 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(hostView);
ActivityOptionsWrapper activityOptions = mLauncher.getAppTransitionManager()
.getActivityLaunchOptions(mLauncher, hostView);
Object itemInfo = hostView.getTag();
if (itemInfo instanceof ItemInfo) {
mLauncher.addLaunchCookie((ItemInfo) itemInfo, activityOptions.options);
}
options = Pair.create(options.first, activityOptions.options);
return RemoteViews.startPendingIntent(hostView, pendingIntent, options);
}

View File

@@ -30,9 +30,11 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.IBinder;
import android.os.UserHandle;
import android.util.Size;
import android.view.View;
import androidx.annotation.NonNull;
@@ -50,9 +52,11 @@ import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.ObjectWrapper;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.quickstep.util.AppCloseConfig;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.views.FloatingWidgetView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.plugins.ResourceProvider;
@@ -77,155 +81,206 @@ public class LauncherSwipeHandlerV2 extends
@Override
protected HomeAnimationFactory createHomeAnimationFactory(ArrayList<IBinder> launchCookies,
long duration) {
HomeAnimationFactory homeAnimFactory;
if (mActivity != null) {
final View workspaceView = findWorkspaceView(launchCookies,
mRecentsView.getRunningTaskView());
boolean canUseWorkspaceView =
workspaceView != null && workspaceView.isAttachedToWindow();
mActivity.getRootView().setForceHideBackArrow(true);
mActivity.setHintUserWillBeActive();
if (canUseWorkspaceView) {
final ResourceProvider rp = DynamicResource.provider(mActivity);
final float transY = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp));
float dpPerSecond = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp_per_s));
final float launcherAlphaMax =
rp.getFloat(R.dimen.swipe_up_launcher_alpha_max_progress);
RectF iconLocation = new RectF();
FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView,
true /* hideOriginal */, iconLocation, false /* isOpening */);
// We want the window alpha to be 0 once this threshold is met, so that the
// FolderIconView can be seen morphing into the icon shape.
float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
homeAnimFactory = new LauncherHomeAnimationFactory() {
// There is a delay in loading the icon, so we need to keep the window
// opaque until it is ready.
private boolean mIsFloatingIconReady = false;
private @Nullable ValueAnimator mBounceBackAnimator;
@Override
public RectF getWindowTargetRect() {
if (PROTOTYPE_APP_CLOSE.get()) {
// We want the target rect to be at this offset position, so that all
// launcher content can spring back upwards.
floatingIconView.setPositionOffsetY(transY);
}
return iconLocation;
}
@Override
public void setAnimation(RectFSpringAnim anim) {
anim.addAnimatorListener(floatingIconView);
floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
floatingIconView.setFastFinishRunnable(anim::end);
if (PROTOTYPE_APP_CLOSE.get()) {
mBounceBackAnimator = bounceBackToRestingPosition();
// Use a spring to put drag layer translation back to 0.
anim.addAnimatorListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
floatingIconView.setPositionOffsetY(0);
mBounceBackAnimator.start();
}
});
Workspace workspace = mActivity.getWorkspace();
workspace.setPivotToScaleWithSelf(mActivity.getHotseat());
}
}
private ValueAnimator bounceBackToRestingPosition() {
DragLayer dl = mActivity.getDragLayer();
Workspace workspace = mActivity.getWorkspace();
Hotseat hotseat = mActivity.getHotseat();
final float startValue = transY;
final float endValue = 0;
// Ensures the velocity is always aligned with the direction.
float pixelPerSecond = Math.abs(dpPerSecond)
* Math.signum(endValue - transY);
ValueAnimator springTransY = new SpringAnimationBuilder(dl.getContext())
.setStiffness(rp.getFloat(R.dimen.swipe_up_trans_y_stiffness))
.setDampingRatio(rp.getFloat(R.dimen.swipe_up_trans_y_damping))
.setMinimumVisibleChange(1f)
.setStartValue(startValue)
.setEndValue(endValue)
.setStartVelocity(pixelPerSecond)
.build(dl, VIEW_TRANSLATE_Y);
springTransY.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
dl.setTranslationY(0f);
dl.setAlpha(1f);
SCALE_PROPERTY.set(workspace, 1f);
SCALE_PROPERTY.set(hotseat, 1f);
}
});
return springTransY;
}
@Override
public boolean keepWindowOpaque() {
if (mIsFloatingIconReady || floatingIconView.isVisibleToUser()) {
mIsFloatingIconReady = true;
return false;
}
return true;
}
@Override
public void update(@Nullable AppCloseConfig config, RectF currentRect,
float progress, float radius) {
int fgAlpha = 255;
if (config != null && PROTOTYPE_APP_CLOSE.get()) {
DragLayer dl = mActivity.getDragLayer();
float translationY = config.getWorkspaceTransY();
dl.setTranslationY(translationY);
float alpha = mapToRange(progress, 0, launcherAlphaMax, 0, 1f, LINEAR);
dl.setAlpha(Math.min(alpha, 1f));
float scale = Math.min(1f, config.getWorkspaceScale());
SCALE_PROPERTY.set(mActivity.getWorkspace(), scale);
SCALE_PROPERTY.set(mActivity.getHotseat(), scale);
SCALE_PROPERTY.set(mActivity.getAppsView(), scale);
progress = config.getInterpolatedProgress();
fgAlpha = config.getFgAlpha();
}
floatingIconView.update(1f, fgAlpha, currentRect, progress,
windowAlphaThreshold, radius, false);
}
@Override
public void onCancel() {
floatingIconView.fastFinish();
if (mBounceBackAnimator != null) {
mBounceBackAnimator.cancel();
}
}
};
} else {
homeAnimFactory = new LauncherHomeAnimationFactory();
}
} else {
homeAnimFactory = new HomeAnimationFactory() {
if (mActivity == null) {
mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
return new HomeAnimationFactory() {
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
}
};
mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
}
return homeAnimFactory;
final View workspaceView = findWorkspaceView(launchCookies,
mRecentsView.getRunningTaskView());
boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
mActivity.getRootView().setForceHideBackArrow(true);
mActivity.setHintUserWillBeActive();
if (!canUseWorkspaceView) {
return new LauncherHomeAnimationFactory();
}
if (workspaceView instanceof LauncherAppWidgetHostView) {
return createWidgetHomeAnimationFactory((LauncherAppWidgetHostView) workspaceView);
}
return createIconHomeAnimationFactory(workspaceView);
}
private HomeAnimationFactory createIconHomeAnimationFactory(View workspaceView) {
final ResourceProvider rp = DynamicResource.provider(mActivity);
final float transY = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp));
float dpPerSecond = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp_per_s));
final float launcherAlphaMax =
rp.getFloat(R.dimen.swipe_up_launcher_alpha_max_progress);
RectF iconLocation = new RectF();
FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView,
true /* hideOriginal */, iconLocation, false /* isOpening */);
// We want the window alpha to be 0 once this threshold is met, so that the
// FolderIconView can be seen morphing into the icon shape.
float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
return new LauncherHomeAnimationFactory() {
// There is a delay in loading the icon, so we need to keep the window
// opaque until it is ready.
private boolean mIsFloatingIconReady = false;
private @Nullable ValueAnimator mBounceBackAnimator;
@Override
public RectF getWindowTargetRect() {
if (PROTOTYPE_APP_CLOSE.get()) {
// We want the target rect to be at this offset position, so that all
// launcher content can spring back upwards.
floatingIconView.setPositionOffsetY(transY);
}
return iconLocation;
}
@Override
public void setAnimation(RectFSpringAnim anim) {
anim.addAnimatorListener(floatingIconView);
floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
floatingIconView.setFastFinishRunnable(anim::end);
if (PROTOTYPE_APP_CLOSE.get()) {
mBounceBackAnimator = bounceBackToRestingPosition();
// Use a spring to put drag layer translation back to 0.
anim.addAnimatorListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
floatingIconView.setPositionOffsetY(0);
mBounceBackAnimator.start();
}
});
Workspace workspace = mActivity.getWorkspace();
workspace.setPivotToScaleWithSelf(mActivity.getHotseat());
}
}
private ValueAnimator bounceBackToRestingPosition() {
DragLayer dl = mActivity.getDragLayer();
Workspace workspace = mActivity.getWorkspace();
Hotseat hotseat = mActivity.getHotseat();
final float startValue = transY;
final float endValue = 0;
// Ensures the velocity is always aligned with the direction.
float pixelPerSecond = Math.abs(dpPerSecond) * Math.signum(endValue - transY);
ValueAnimator springTransY = new SpringAnimationBuilder(dl.getContext())
.setStiffness(rp.getFloat(R.dimen.swipe_up_trans_y_stiffness))
.setDampingRatio(rp.getFloat(R.dimen.swipe_up_trans_y_damping))
.setMinimumVisibleChange(1f)
.setStartValue(startValue)
.setEndValue(endValue)
.setStartVelocity(pixelPerSecond)
.build(dl, VIEW_TRANSLATE_Y);
springTransY.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
dl.setTranslationY(0f);
dl.setAlpha(1f);
SCALE_PROPERTY.set(workspace, 1f);
SCALE_PROPERTY.set(hotseat, 1f);
}
});
return springTransY;
}
@Override
public boolean keepWindowOpaque() {
if (mIsFloatingIconReady || floatingIconView.isVisibleToUser()) {
mIsFloatingIconReady = true;
return false;
}
return true;
}
@Override
public void update(@Nullable AppCloseConfig config, RectF currentRect,
float progress, float radius) {
int fgAlpha = 255;
if (config != null && PROTOTYPE_APP_CLOSE.get()) {
DragLayer dl = mActivity.getDragLayer();
float translationY = config.getWorkspaceTransY();
dl.setTranslationY(translationY);
float alpha = mapToRange(progress, 0, launcherAlphaMax, 0, 1f, LINEAR);
dl.setAlpha(Math.min(alpha, 1f));
float scale = Math.min(1f, config.getWorkspaceScale());
SCALE_PROPERTY.set(mActivity.getWorkspace(), scale);
SCALE_PROPERTY.set(mActivity.getHotseat(), scale);
SCALE_PROPERTY.set(mActivity.getAppsView(), scale);
progress = config.getInterpolatedProgress();
fgAlpha = config.getFgAlpha();
}
floatingIconView.update(1f, fgAlpha, currentRect, progress,
windowAlphaThreshold, radius, false);
}
@Override
public void onCancel() {
floatingIconView.fastFinish();
if (mBounceBackAnimator != null) {
mBounceBackAnimator.cancel();
}
}
};
}
private HomeAnimationFactory createWidgetHomeAnimationFactory(
LauncherAppWidgetHostView hostView) {
RectF backgroundLocation = new RectF();
Rect crop = new Rect();
mTaskViewSimulator.getCurrentCropRect().roundOut(crop);
Size windowSize = new Size(crop.width(), crop.height());
FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mActivity,
hostView, backgroundLocation, windowSize,
mTaskViewSimulator.getCurrentCornerRadius());
return new LauncherHomeAnimationFactory() {
@Override
public RectF getWindowTargetRect() {
return backgroundLocation;
}
@Override
public float getEndRadius(RectF cropRectF) {
return floatingWidgetView.getInitialCornerRadius();
}
@Override
public void setAnimation(RectFSpringAnim anim) {
anim.addAnimatorListener(floatingWidgetView);
floatingWidgetView.setFastFinishRunnable(anim::end);
}
@Override
public boolean keepWindowOpaque() {
return false;
}
@Override
public void update(@Nullable AppCloseConfig config, RectF currentRect,
float progress, float radius) {
floatingWidgetView.update(currentRect, 1 /* floatingWidgetAlpha */,
config != null ? config.getFgAlpha() : 1f /* foregroundAlpha */,
0 /* fallbackBackgroundAlpha */, 1 - progress);
}
@Override
public void onCancel() {
floatingWidgetView.fastFinish();
}
};
}
/**

View File

@@ -145,6 +145,11 @@ public abstract class SwipeUpAnimationLogic {
targetX + halfIconSize, targetY + halfIconSize);
}
/** Returns the corner radius of the window at the end of the animation. */
public float getEndRadius(RectF cropRectF) {
return cropRectF.width() / 2f;
}
public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome();
public void playAtomicAnimation(float velocity) {
@@ -197,8 +202,7 @@ public abstract class SwipeUpAnimationLogic {
final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
Matrix homeToWindowPositionMap = new Matrix();
final RectF startRect = updateProgressForStartRect(
homeToWindowPositionMap, startProgress);
final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
// Move the startRect to Launcher space as floatingIconView runs in Launcher
@@ -210,7 +214,7 @@ public abstract class SwipeUpAnimationLogic {
if (PROTOTYPE_APP_CLOSE.get()) {
anim = new RectFSpringAnim2(startRect, targetRect, mContext,
mTaskViewSimulator.getCurrentCornerRadius(),
cropRectF.width() / 2f);
homeAnimationFactory.getEndRadius(cropRectF));
} else {
anim = new RectFSpringAnim(startRect, targetRect, mContext);
}
@@ -269,7 +273,7 @@ public abstract class SwipeUpAnimationLogic {
// End on a "round-enough" radius so that the shape reveal doesn't have to do too much
// rounding at the end of the animation.
mStartRadius = mTaskViewSimulator.getCurrentCornerRadius();
mEndRadius = cropRectF.width() / 2f;
mEndRadius = factory.getEndRadius(cropRectF);
}
@Override

View File

@@ -20,10 +20,10 @@ import android.animation.Animator.AnimatorListener;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Size;
import android.view.GhostView;
import android.view.View;
import android.view.ViewGroup;
@@ -113,7 +113,7 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener
}
private void init(DragLayer dragLayer, LauncherAppWidgetHostView originalView,
RectF widgetBackgroundPosition, Rect windowTargetBounds, float windowCornerRadius) {
RectF widgetBackgroundPosition, Size windowSize, float windowCornerRadius) {
mAppWidgetView = originalView;
mAppWidgetView.beginDeferringUpdates();
mBackgroundPosition = widgetBackgroundPosition;
@@ -128,7 +128,7 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener
getRelativePosition(mAppWidgetBackgroundView, mAppWidgetView, mBackgroundOffset);
mBackgroundView.init(mAppWidgetView, mAppWidgetBackgroundView, windowCornerRadius);
// Layout call before GhostView creation so that the overlaid view isn't clipped
layout(0, 0, windowTargetBounds.width(), windowTargetBounds.height());
layout(0, 0, windowSize.getWidth(), windowSize.getHeight());
mForegroundOverlayView = GhostView.addGhost(mAppWidgetView, this);
positionViews();
@@ -219,19 +219,19 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener
*
* @param widgetBackgroundPosition a {@link RectF} that will be updated with the widget's
* background bounds
* @param windowTargetBounds the bounds of the window when launched
* @param windowSize the size of the window when launched
* @param windowCornerRadius the corner radius of the window
*/
public static FloatingWidgetView getFloatingWidgetView(Launcher launcher,
LauncherAppWidgetHostView originalView, RectF widgetBackgroundPosition,
Rect windowTargetBounds, float windowCornerRadius) {
Size windowSize, float windowCornerRadius) {
final DragLayer dragLayer = launcher.getDragLayer();
ViewGroup parent = (ViewGroup) dragLayer.getParent();
FloatingWidgetView floatingView =
launcher.getViewCache().getView(R.layout.floating_widget_view, launcher, parent);
floatingView.recycle();
floatingView.init(dragLayer, originalView, widgetBackgroundPosition, windowTargetBounds,
floatingView.init(dragLayer, originalView, widgetBackgroundPosition, windowSize,
windowCornerRadius);
parent.addView(floatingView);
return floatingView;
@@ -240,7 +240,7 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener
private static void getRelativePosition(View descendant, View ancestor, RectF position) {
float[] points = new float[]{0, 0, descendant.getWidth(), descendant.getHeight()};
Utilities.getDescendantCoordRelativeToAncestor(descendant, ancestor, points,
false /* includeRootScroll */);
false /* includeRootScroll */, true /* ignoreTransform */);
position.set(
Math.min(points[0], points[2]),
Math.min(points[1], points[3]),