diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 75b8796c9e..66e20d75a0 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -64,6 +64,7 @@ import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATI import static com.android.launcher3.views.FloatingIconView.getFloatingIconView; import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; +import static com.android.quickstep.util.AnimUtils.clampToDuration; import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback; import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius; import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows; @@ -748,34 +749,35 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius; MultiValueUpdateListener listener = new MultiValueUpdateListener() { - FloatProp mDx = new FloatProp(0, prop.dX, 0, APP_LAUNCH_DURATION, - mOpeningXInterpolator); - FloatProp mDy = new FloatProp(0, prop.dY, 0, APP_LAUNCH_DURATION, - mOpeningInterpolator); + FloatProp mDx = new FloatProp(0, prop.dX, mOpeningXInterpolator); + FloatProp mDy = new FloatProp(0, prop.dY, mOpeningInterpolator); FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale, - prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, mOpeningInterpolator); + prop.finalAppIconScale, mOpeningInterpolator); FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f, - APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, LINEAR); + clampToDuration(LINEAR, APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, + APP_LAUNCH_DURATION)); - FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0, - APP_LAUNCH_DURATION, mOpeningInterpolator); - FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0, - APP_LAUNCH_DURATION, mOpeningInterpolator); + FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, + mOpeningInterpolator); + FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, + mOpeningInterpolator); FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd, - 0, APP_LAUNCH_DURATION, mOpeningInterpolator); + mOpeningInterpolator); FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd, - 0, APP_LAUNCH_DURATION, mOpeningInterpolator); - FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, 0, - APP_LAUNCH_DURATION, mOpeningInterpolator); - FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 0, - APP_LAUNCH_DURATION, mOpeningInterpolator); + mOpeningInterpolator); + FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, + mOpeningInterpolator); + FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, + mOpeningInterpolator); - FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION, - NAV_FADE_OUT_INTERPOLATOR); - FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN, - ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); + FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration( + NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION, + APP_LAUNCH_DURATION)); + FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration( + NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN, + ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION)); @Override public void onUpdate(float percent, boolean initOnly) { @@ -968,37 +970,36 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener appAnimator.addUpdateListener(new MultiValueUpdateListener() { float mAppWindowScale = 1; - final FloatProp mWidgetForegroundAlpha = new FloatProp(1 /* start */, - 0 /* end */, 0 /* delay */, - WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR); - final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0 /* start */, - 1 /* end */, 0 /* delay */, 75 /* duration */, LINEAR); - final FloatProp mPreviewAlpha = new FloatProp(0 /* start */, 1 /* end */, + final FloatProp mWidgetForegroundAlpha = new FloatProp(1, 0, clampToDuration( + LINEAR, 0, WIDGET_CROSSFADE_DURATION_MILLIS / 2, APP_LAUNCH_DURATION)); + + final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0, 1, + clampToDuration(LINEAR, 0, 75, APP_LAUNCH_DURATION)); + final FloatProp mPreviewAlpha = new FloatProp(0, 1, clampToDuration( + LINEAR, WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */, - WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR); + WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, + APP_LAUNCH_DURATION)); final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, - 0 /* start */, APP_LAUNCH_DURATION, mOpeningInterpolator); - final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, APP_LAUNCH_DURATION, mOpeningInterpolator); + final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, mOpeningInterpolator); // Window & widget background positioning bounds final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(), - windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_DURATION, - mOpeningXInterpolator); + windowTargetBounds.centerX(), mOpeningXInterpolator); final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(), - windowTargetBounds.centerY(), 0 /* delay */, APP_LAUNCH_DURATION, - mOpeningInterpolator); + windowTargetBounds.centerY(), mOpeningInterpolator); final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(), - windowTargetBounds.width(), 0 /* delay */, APP_LAUNCH_DURATION, - mOpeningInterpolator); + windowTargetBounds.width(), mOpeningInterpolator); final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(), - windowTargetBounds.height(), 0 /* delay */, APP_LAUNCH_DURATION, - mOpeningInterpolator); + windowTargetBounds.height(), mOpeningInterpolator); - final FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION, - NAV_FADE_OUT_INTERPOLATOR); - final FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN, - ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); + final FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration( + NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION, + APP_LAUNCH_DURATION)); + final FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration( + NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN, + ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION)); @Override public void onUpdate(float percent, boolean initOnly) { @@ -1508,11 +1509,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius; closingAnimator.setDuration(duration); closingAnimator.addUpdateListener(new MultiValueUpdateListener() { - FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DECELERATE_1_7); - FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DECELERATE_1_7); - FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR); - FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, 0, duration, - DECELERATE_1_7); + FloatProp mDy = new FloatProp(0, mClosingWindowTransY, DECELERATE_1_7); + FloatProp mScale = new FloatProp(1f, 1f, DECELERATE_1_7); + FloatProp mAlpha = new FloatProp(1f, 0f, clampToDuration(LINEAR, 25, 125, duration)); + FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, DECELERATE_1_7); @Override public void onUpdate(float percent, boolean initOnly) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java index faa67be895..189b6872d2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java @@ -686,15 +686,10 @@ public class TaskbarDragController extends DragController im float toScale = iconSize / mDragIconSize; float toAlpha = (target == originalView) ? 1f : 0f; MultiValueUpdateListener listener = new MultiValueUpdateListener() { - final FloatProp mDx = new FloatProp(fromX, toPosition[0], 0, - ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.FAST_OUT_SLOW_IN); - final FloatProp mDy = new FloatProp(fromY, toPosition[1], 0, - ANIM_DURATION_RETURN_ICON_TO_TASKBAR, - FAST_OUT_SLOW_IN); - final FloatProp mScale = new FloatProp(1f, toScale, 0, - ANIM_DURATION_RETURN_ICON_TO_TASKBAR, FAST_OUT_SLOW_IN); - final FloatProp mAlpha = new FloatProp(1f, toAlpha, 0, - ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.ACCELERATE_2); + final FloatProp mDx = new FloatProp(fromX, toPosition[0], FAST_OUT_SLOW_IN); + final FloatProp mDy = new FloatProp(fromY, toPosition[1], FAST_OUT_SLOW_IN); + final FloatProp mScale = new FloatProp(1f, toScale, FAST_OUT_SLOW_IN); + final FloatProp mAlpha = new FloatProp(1f, toAlpha, Interpolators.ACCELERATE_2); @Override public void onUpdate(float percent, boolean initOnly) { animListener.updateDragShadow(mDx.value, mDy.value, mScale.value, mAlpha.value); diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java index e30ea7a26f..8d4255c416 100644 --- a/quickstep/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java @@ -37,6 +37,7 @@ import static com.android.launcher3.QuickstepTransitionManager.SPLIT_DIVIDER_ANI import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION; import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE; +import static com.android.quickstep.util.AnimUtils.clampToDuration; import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported; import android.animation.Animator; @@ -267,10 +268,16 @@ public final class TaskViewUtils { if (navBarTarget != null) { final Rect cropRect = new Rect(); out.addOnFrameListener(new MultiValueUpdateListener() { - FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, - ANIMATION_NAV_FADE_OUT_DURATION, NAV_FADE_OUT_INTERPOLATOR); - FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN, - ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); + FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration( + NAV_FADE_OUT_INTERPOLATOR, + 0, + ANIMATION_NAV_FADE_OUT_DURATION, + out.getDuration())); + FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration( + NAV_FADE_IN_INTERPOLATOR, + ANIMATION_DELAY_NAV_FADE_IN, + ANIMATION_NAV_FADE_IN_DURATION, + out.getDuration())); @Override public void onUpdate(float percent, boolean initOnly) { diff --git a/quickstep/src/com/android/quickstep/util/AnimUtils.java b/quickstep/src/com/android/quickstep/util/AnimUtils.java index 1f2a02c816..8e3d44f8d8 100644 --- a/quickstep/src/com/android/quickstep/util/AnimUtils.java +++ b/quickstep/src/com/android/quickstep/util/AnimUtils.java @@ -16,10 +16,12 @@ package com.android.quickstep.util; +import static com.android.app.animation.Interpolators.clampToProgress; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.os.Bundle; import android.os.IRemoteCallback; +import android.view.animation.Interpolator; import com.android.launcher3.util.RunnableList; @@ -67,4 +69,15 @@ public class AnimUtils { } }; } + + /** + * Returns a function that runs the given interpolator such that the entire progress is set + * between the given duration. That is, we set the interpolation to 0 until startDelay and reach + * 1 by (startDelay + duration). + */ + public static Interpolator clampToDuration(Interpolator interpolator, float startDelay, + float duration, float totalDuration) { + return clampToProgress(interpolator, startDelay / totalDuration, + (startDelay + duration) / totalDuration); + } } diff --git a/quickstep/src/com/android/quickstep/util/AppCloseConfig.java b/quickstep/src/com/android/quickstep/util/AppCloseConfig.java deleted file mode 100644 index bec33797a3..0000000000 --- a/quickstep/src/com/android/quickstep/util/AppCloseConfig.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2021 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.quickstep.util; - -import android.annotation.FloatRange; -import android.annotation.IntRange; - -/* - * Adds getter methods to {@link MultiValueUpdateListener} specific to app close animation, - * so that the entire animation can be defined in one place. - */ -public abstract class AppCloseConfig extends MultiValueUpdateListener { - - /** - * Returns the translation y of the workspace contents. - */ - public abstract float getWorkspaceTransY(); - - /* - * Returns the scale of the workspace contents. - */ - public abstract float getWorkspaceScale(); - - /* - * Returns the alpha of the window. - */ - public abstract @FloatRange(from = 0, to = 1) float getWindowAlpha(); - - /* - * Returns the alpha of the foreground layer of an adaptive icon. - */ - public abstract @IntRange(from = 0, to = 255) int getFgAlpha(); - - /* - * Returns the corner radius of the window and icon. - */ - public abstract float getCornerRadius(); - - /* - * Returns the interpolated progress of the animation. - */ - public abstract float getInterpolatedProgress(); - -} diff --git a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java index 1c3c9c2fae..72fc2a67b5 100644 --- a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java +++ b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java @@ -18,6 +18,8 @@ package com.android.quickstep.util; import android.animation.ValueAnimator; import android.view.animation.Interpolator; +import com.android.launcher3.Utilities; + import java.util.ArrayList; /** @@ -31,14 +33,11 @@ public abstract class MultiValueUpdateListener implements ValueAnimator.Animator @Override public final void onAnimationUpdate(ValueAnimator animator) { final float percent = animator.getAnimatedFraction(); - final float currentPlayTime = percent * animator.getDuration(); for (int i = mAllProperties.size() - 1; i >= 0; i--) { FloatProp prop = mAllProperties.get(i); - float time = Math.max(0, currentPlayTime - prop.mDelay); - float newPercent = Math.min(1f, time / prop.mDuration); - newPercent = prop.mInterpolator.getInterpolation(newPercent); - prop.value = prop.mEnd * newPercent + prop.mStart * (1 - newPercent); + float interpolatedPercent = prop.mInterpolator.getInterpolation(percent); + prop.value = Utilities.mapRange(interpolatedPercent, prop.mStart, prop.mEnd); } onUpdate(percent, false /* initOnly */); } @@ -55,17 +54,12 @@ public abstract class MultiValueUpdateListener implements ValueAnimator.Animator private final float mStart; private final float mEnd; - private final float mDelay; - private final float mDuration; private final Interpolator mInterpolator; - public FloatProp(float start, float end, float delay, float duration, Interpolator i) { + public FloatProp(float start, float end, Interpolator i) { value = mStart = start; mEnd = end; - mDelay = delay; - mDuration = duration; mInterpolator = i; - mAllProperties.add(this); } diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt index b7b1d8f754..8f5c9c1186 100644 --- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt +++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt @@ -70,69 +70,84 @@ import java.util.Optional import java.util.function.Supplier /** - * Utils class to help run animations for initiating split screen from launcher. - * Will be expanded with future refactors. Works in conjunction with the state stored in - * [SplitSelectStateController] + * Utils class to help run animations for initiating split screen from launcher. Will be expanded + * with future refactors. Works in conjunction with the state stored in [SplitSelectStateController] */ class SplitAnimationController(val splitSelectStateController: SplitSelectStateController) { companion object { // Break this out into maybe enums? Abstractions into its own classes? Tbd. data class SplitAnimInitProps( - val originalView: View, - val originalBitmap: Bitmap?, - val iconDrawable: Drawable, - val fadeWithThumbnail: Boolean, - val isStagedTask: Boolean, - val iconView: View? + val originalView: View, + val originalBitmap: Bitmap?, + val iconDrawable: Drawable, + val fadeWithThumbnail: Boolean, + val isStagedTask: Boolean, + val iconView: View? ) } /** - * Returns different elements to animate for the initial split selection animation - * depending on the state of the surface from which the split was initiated + * Returns different elements to animate for the initial split selection animation depending on + * the state of the surface from which the split was initiated */ - fun getFirstAnimInitViews(taskViewSupplier: Supplier, - splitSelectSourceSupplier: Supplier) - : SplitAnimInitProps { + fun getFirstAnimInitViews( + taskViewSupplier: Supplier, + splitSelectSourceSupplier: Supplier + ): SplitAnimInitProps { val splitSelectSource = splitSelectSourceSupplier.get() if (!splitSelectStateController.isAnimateCurrentTaskDismissal) { // Initiating from home - return SplitAnimInitProps(splitSelectSource!!.view, originalBitmap = null, - splitSelectSource.drawable, fadeWithThumbnail = false, isStagedTask = true, - iconView = null) + return SplitAnimInitProps( + splitSelectSource!!.view, + originalBitmap = null, + splitSelectSource.drawable, + fadeWithThumbnail = false, + isStagedTask = true, + iconView = null + ) } else if (splitSelectStateController.isDismissingFromSplitPair) { // Initiating split from overview, but on a split pair val taskView = taskViewSupplier.get() - for (container : TaskIdAttributeContainer in taskView.taskIdAttributeContainers) { + for (container: TaskIdAttributeContainer in taskView.taskIdAttributeContainers) { if (container.task.getKey().getId() == splitSelectStateController.initialTaskId) { val drawable = getDrawable(container.iconView, splitSelectSource) - return SplitAnimInitProps(container.thumbnailView, - container.thumbnailView.thumbnail, drawable!!, - fadeWithThumbnail = true, isStagedTask = true, - iconView = container.iconView.asView() + return SplitAnimInitProps( + container.thumbnailView, + container.thumbnailView.thumbnail, + drawable!!, + fadeWithThumbnail = true, + isStagedTask = true, + iconView = container.iconView.asView() ) } } - throw IllegalStateException("Attempting to init split from existing split pair " + - "without a valid taskIdAttributeContainer") + throw IllegalStateException( + "Attempting to init split from existing split pair " + + "without a valid taskIdAttributeContainer" + ) } else { // Initiating split from overview on fullscreen task TaskView val taskView = taskViewSupplier.get() val drawable = getDrawable(taskView.iconView, splitSelectSource) - return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail, - drawable!!, fadeWithThumbnail = true, isStagedTask = true, - taskView.iconView.asView() + return SplitAnimInitProps( + taskView.thumbnail, + taskView.thumbnail.thumbnail, + drawable!!, + fadeWithThumbnail = true, + isStagedTask = true, + taskView.iconView.asView() ) } } /** - * Returns the drawable that's provided in iconView, however if that - * is null it falls back to the drawable that's in splitSelectSource. - * TaskView's icon drawable can be null if the TaskView is scrolled far enough off screen + * Returns the drawable that's provided in iconView, however if that is null it falls back to + * the drawable that's in splitSelectSource. TaskView's icon drawable can be null if the + * TaskView is scrolled far enough off screen + * * @return [Drawable] */ - fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?) : Drawable? { + fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?): Drawable? { if (iconView.drawable == null && splitSelectSource != null) { return splitSelectSource.drawable } @@ -140,21 +155,25 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC } /** - * When selecting first app from split pair, second app's thumbnail remains. This animates - * the second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying - * it with [TaskThumbnailView]'s splashView. Adds animations to the provided builder. - * Note: The app that **was not** selected as the first split app should be the container that's - * passed through. + * When selecting first app from split pair, second app's thumbnail remains. This animates the + * second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying it + * with [TaskThumbnailView]'s splashView. Adds animations to the provided builder. Note: The app + * that **was not** selected as the first split app should be the container that's passed + * through. * * @param builder Adds animation to this * @param taskIdAttributeContainer container of the app that **was not** selected * @param isPrimaryTaskSplitting if true, task that was split would be top/left in the pair - * (opposite of that representing [taskIdAttributeContainer]) + * (opposite of that representing [taskIdAttributeContainer]) */ - fun addInitialSplitFromPair(taskIdAttributeContainer: TaskIdAttributeContainer, - builder: PendingAnimation, deviceProfile: DeviceProfile, - taskViewWidth: Int, taskViewHeight: Int, - isPrimaryTaskSplitting: Boolean) { + fun addInitialSplitFromPair( + taskIdAttributeContainer: TaskIdAttributeContainer, + builder: PendingAnimation, + deviceProfile: DeviceProfile, + taskViewWidth: Int, + taskViewHeight: Int, + isPrimaryTaskSplitting: Boolean + ) { val thumbnail = taskIdAttributeContainer.thumbnailView val iconView: View = taskIdAttributeContainer.iconView.asView() builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f)) @@ -170,35 +189,42 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC ) ) builder.add( - ObjectAnimator.ofFloat( - iconView.splitTranslationY, - MULTI_PROPERTY_VALUE, - 0f - ) + ObjectAnimator.ofFloat(iconView.splitTranslationY, MULTI_PROPERTY_VALUE, 0f) ) } if (deviceProfile.isLeftRightSplit) { // Center view first so scaling happens uniformly, alternatively we can move pivotX to 0 val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width - builder.add(ObjectAnimator.ofFloat(thumbnail, - TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX)) + builder.add( + ObjectAnimator.ofFloat( + thumbnail, + TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, + centerThumbnailTranslationX + ) + ) if (!enableOverviewIconMenu()) { // icons are anchored from Gravity.END, so need to use negative translation val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f - builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, - -centerIconTranslationX)) + builder.add( + ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, -centerIconTranslationX) + ) } builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX)) // Reset other dimensions // TODO(b/271468547), can't set Y translate to 0, need to account for top space thumbnail.scaleY = 1f - val translateYResetVal: Float = if (!isPrimaryTaskSplitting) 0f else - deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat() - builder.add(ObjectAnimator.ofFloat(thumbnail, + val translateYResetVal: Float = + if (!isPrimaryTaskSplitting) 0f + else deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat() + builder.add( + ObjectAnimator.ofFloat( + thumbnail, TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, - translateYResetVal)) + translateYResetVal + ) + ) } else { val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx // Center view first so scaling happens uniformly, alternatively we can move pivotY to 0 @@ -214,16 +240,21 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC // translations otherwise this asymmetry causes problems.. if (isPrimaryTaskSplitting) { centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f - centerThumbnailTranslationY += deviceProfile.overviewTaskThumbnailTopMarginPx - .toFloat() + centerThumbnailTranslationY += + deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat() } else { centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f } val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height - builder.add(ObjectAnimator.ofFloat(thumbnail, - TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY)) + builder.add( + ObjectAnimator.ofFloat( + thumbnail, + TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, + centerThumbnailTranslationY + ) + ) - if (!enableOverviewIconMenu()) { + if (!enableOverviewIconMenu()) { // icons are anchored from Gravity.END, so need to use negative translation builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, 0f)) } @@ -231,8 +262,9 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC // Reset other dimensions thumbnail.scaleX = 1f - builder.add(ObjectAnimator.ofFloat(thumbnail, - TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f)) + builder.add( + ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f) + ) } } @@ -250,69 +282,94 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC * Returns [AnimatorSet] which slides initial split placeholder view offscreen and logs an event * for why split is being dismissed */ - fun createPlaceholderDismissAnim(launcher: StatefulActivity<*>, - splitDismissEvent: EventEnum, - duration: Long?) : AnimatorSet { + fun createPlaceholderDismissAnim( + launcher: StatefulActivity<*>, + splitDismissEvent: EventEnum, + duration: Long? + ): AnimatorSet { val animatorSet = AnimatorSet() duration?.let { animatorSet.duration = it } - val recentsView : RecentsView<*, *> = launcher.getOverviewPanel() - val floatingTask: FloatingTaskView = splitSelectStateController.firstFloatingTaskView - ?: return animatorSet + val recentsView: RecentsView<*, *> = launcher.getOverviewPanel() + val floatingTask: FloatingTaskView = + splitSelectStateController.firstFloatingTaskView ?: return animatorSet // We are in split selection state currently, transitioning to another state val dragLayer: BaseDragLayer<*> = launcher.dragLayer val onScreenRectF = RectF() - Utilities.getBoundsForViewInDragLayer(dragLayer, floatingTask, - Rect(0, 0, floatingTask.width, floatingTask.height), - false, null, onScreenRectF) + Utilities.getBoundsForViewInDragLayer( + dragLayer, + floatingTask, + Rect(0, 0, floatingTask.width, floatingTask.height), + false, + null, + onScreenRectF + ) // Get the part of the floatingTask that intersects with the DragLayer (i.e. the // on-screen portion) onScreenRectF.intersect( - dragLayer.left.toFloat(), - dragLayer.top.toFloat(), - dragLayer.right.toFloat(), - dragLayer.bottom - .toFloat() + dragLayer.left.toFloat(), + dragLayer.top.toFloat(), + dragLayer.right.toFloat(), + dragLayer.bottom.toFloat() ) - animatorSet.play(ObjectAnimator.ofFloat(floatingTask, + animatorSet.play( + ObjectAnimator.ofFloat( + floatingTask, FloatingTaskView.PRIMARY_TRANSLATE_OFFSCREEN, - recentsView.pagedOrientationHandler - .getFloatingTaskOffscreenTranslationTarget( - floatingTask, - onScreenRectF, - floatingTask.stagePosition, - launcher.deviceProfile - ))) - animatorSet.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - splitSelectStateController.resetState() - safeRemoveViewFromDragLayer(launcher, - splitSelectStateController.splitInstructionsView) + recentsView.pagedOrientationHandler.getFloatingTaskOffscreenTranslationTarget( + floatingTask, + onScreenRectF, + floatingTask.stagePosition, + launcher.deviceProfile + ) + ) + ) + animatorSet.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + splitSelectStateController.resetState() + safeRemoveViewFromDragLayer( + launcher, + splitSelectStateController.splitInstructionsView + ) + } } - }) + ) splitSelectStateController.logExitReason(splitDismissEvent) return animatorSet } /** - * Returns a [PendingAnimation] to animate in the chip to instruct a user to select a second - * app for splitscreen + * Returns a [PendingAnimation] to animate in the chip to instruct a user to select a second app + * for splitscreen */ - fun getShowSplitInstructionsAnim(launcher: StatefulActivity<*>) : PendingAnimation { + fun getShowSplitInstructionsAnim(launcher: StatefulActivity<*>): PendingAnimation { safeRemoveViewFromDragLayer(launcher, splitSelectStateController.splitInstructionsView) val splitInstructionsView = SplitInstructionsView.getSplitInstructionsView(launcher) splitSelectStateController.splitInstructionsView = splitInstructionsView val timings = AnimUtils.getDeviceOverviewToSplitTimings(launcher.deviceProfile.isTablet) val anim = PendingAnimation(100 /*duration */) splitInstructionsView.alpha = 0f - anim.setViewAlpha(splitInstructionsView, 1f, - Interpolators.clampToProgress(Interpolators.LINEAR, - timings.instructionsContainerFadeInStartOffset, - timings.instructionsContainerFadeInEndOffset)) - anim.addFloat(splitInstructionsView, SplitInstructionsView.UNFOLD, 0.1f, 1f, - Interpolators.clampToProgress(Interpolators.EMPHASIZED_DECELERATE, - timings.instructionsUnfoldStartOffset, - timings.instructionsUnfoldEndOffset)) + anim.setViewAlpha( + splitInstructionsView, + 1f, + Interpolators.clampToProgress( + Interpolators.LINEAR, + timings.instructionsContainerFadeInStartOffset, + timings.instructionsContainerFadeInEndOffset + ) + ) + anim.addFloat( + splitInstructionsView, + SplitInstructionsView.UNFOLD, + 0.1f, + 1f, + Interpolators.clampToProgress( + Interpolators.EMPHASIZED_DECELERATE, + timings.instructionsUnfoldStartOffset, + timings.instructionsUnfoldEndOffset + ) + ) return anim } @@ -323,15 +380,20 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC /** * Animates the first placeholder view to fullscreen and launches its task. + * * TODO(b/276361926): Remove the [resetCallback] option once contextual launches */ - fun playAnimPlaceholderToFullscreen(launcher: StatefulActivity<*>, view: View, - resetCallback: Optional) { + fun playAnimPlaceholderToFullscreen( + launcher: StatefulActivity<*>, + view: View, + resetCallback: Optional + ) { val stagedTaskView = view as FloatingTaskView val isTablet: Boolean = launcher.deviceProfile.isTablet - val duration = if (isTablet) SplitAnimationTimings.TABLET_CONFIRM_DURATION else - SplitAnimationTimings.PHONE_CONFIRM_DURATION + val duration = + if (isTablet) SplitAnimationTimings.TABLET_CONFIRM_DURATION + else SplitAnimationTimings.PHONE_CONFIRM_DURATION val pendingAnimation = PendingAnimation(duration.toLong()) val firstTaskStartingBounds = Rect() val firstTaskEndingBounds = Rect() @@ -341,11 +403,12 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC splitSelectStateController.setLaunchingFirstAppFullscreen() stagedTaskView.addConfirmAnimation( - pendingAnimation, - RectF(firstTaskStartingBounds), - firstTaskEndingBounds, - false /* fadeWithThumbnail */, - true /* isStagedTask */) + pendingAnimation, + RectF(firstTaskStartingBounds), + firstTaskEndingBounds, + false /* fadeWithThumbnail */, + true /* isStagedTask */ + ) pendingAnimation.addEndListener { splitSelectStateController.launchInitialAppFullscreen { @@ -490,8 +553,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC * When the user taps an app pair icon to launch split, this will play the tasks' launch * animation from the position of the icon. * - * To find the root shell leash that we want to fade in, we do the following: - * The Changes we receive in transitionInfo are structured like this + * To find the root shell leash that we want to fade in, we do the following: The Changes we + * receive in transitionInfo are structured like this * * Root (grandparent) * | @@ -503,9 +566,9 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC * | * --> App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW) * - * We want to animate the Root (grandparent) so that it affects both apps and the divider. - * To do this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the - * left-side ones, for simplicity) and traverse the tree until we find the grandparent. + * We want to animate the Root (grandparent) so that it affects both apps and the divider. To do + * this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the left-side ones, + * for simplicity) and traverse the tree until we find the grandparent. * * This function is only called when we are animating the app pair in from scratch. It is NOT * called when we are animating in from an existing visible TaskView tile or an app that is @@ -544,8 +607,10 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC // TODO (b/316490565): Replace this logic when SplitBounds is available to // startAnimation() and we can know the precise taskIds of launching tasks. // Find a change that has WINDOWING_MODE_MULTI_WINDOW. - if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW && - (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)) { + if ( + taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW && + (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) + ) { // Check if it is a left/top app. val isLeftTopApp = (dp.isLeftRightSplit && change.endAbsBounds.left == 0) || @@ -614,8 +679,6 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC FloatProp( floatingView.startingPosition.left, dp.widthPx / 2f - floatingView.startingPosition.width() / 2f, - 0f /* delay */, - timings.getDuration().toFloat(), Interpolators.clampToProgress( timings.getStagedRectXInterpolator(), timings.stagedRectSlideStartOffset, @@ -626,8 +689,6 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC FloatProp( floatingView.startingPosition.top, dp.heightPx / 2f - floatingView.startingPosition.height() / 2f, - 0f /* delay */, - timings.getDuration().toFloat(), Interpolators.clampToProgress( Interpolators.EMPHASIZED, timings.stagedRectSlideStartOffset, @@ -638,8 +699,6 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC FloatProp( 1f /* start */, dp.widthPx / floatingView.startingPosition.width(), - 0f /* delay */, - timings.getDuration().toFloat(), Interpolators.clampToProgress( Interpolators.EMPHASIZED, timings.stagedRectSlideStartOffset, @@ -650,8 +709,6 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC FloatProp( 1f /* start */, dp.heightPx / floatingView.startingPosition.height(), - 0f /* delay */, - timings.getDuration().toFloat(), Interpolators.clampToProgress( Interpolators.EMPHASIZED, timings.stagedRectSlideStartOffset, diff --git a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java index fdc8f1ff14..121d8ede11 100644 --- a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java +++ b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java @@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_EDU_SHOWN; +import static com.android.quickstep.util.AnimUtils.clampToDuration; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -187,10 +188,14 @@ public class AllAppsEduView extends AbstractFloatingView { intro.setInterpolator(LINEAR); intro.setDuration(introDuration); intro.addUpdateListener((new MultiValueUpdateListener() { - FloatProp mCircleAlpha = new FloatProp(0, 255, 0, firstPart, LINEAR); - FloatProp mCircleScale = new FloatProp(2f, 1f, 0, firstPart, OVERSHOOT_1_7); - FloatProp mDeltaY = new FloatProp(0, transY, firstPart, secondPart, FAST_OUT_SLOW_IN); - FloatProp mGradientAlpha = new FloatProp(0, 255, firstPart, secondPart * 0.3f, LINEAR); + FloatProp mCircleAlpha = new FloatProp(0, 255, + clampToDuration(LINEAR, 0, firstPart, introDuration)); + FloatProp mCircleScale = new FloatProp(2f, 1f, + clampToDuration(OVERSHOOT_1_7, 0, firstPart, introDuration)); + FloatProp mDeltaY = new FloatProp(0, transY, + clampToDuration(FAST_OUT_SLOW_IN, firstPart, secondPart, introDuration)); + FloatProp mGradientAlpha = new FloatProp(0, 255, + clampToDuration(LINEAR, firstPart, secondPart * 0.3f, introDuration)); @Override public void onUpdate(float progress, boolean initOnly) { diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java index 12a073fa84..18922a6fb3 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java +++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java @@ -328,20 +328,20 @@ public class FloatingTaskView extends FrameLayout { MultiValueUpdateListener listener = new MultiValueUpdateListener() { // SplitPlaceholderView: rectangle translates and stretches to new position - final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, + final FloatProp mDx = new FloatProp(0, prop.dX, clampToProgress(timings.getStagedRectXInterpolator(), timings.getStagedRectSlideStartOffset(), timings.getStagedRectSlideEndOffset())); - final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, + final FloatProp mDy = new FloatProp(0, prop.dY, clampToProgress(timings.getStagedRectYInterpolator(), timings.getStagedRectSlideStartOffset(), timings.getStagedRectSlideEndOffset())); - final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0, - animDuration, clampToProgress(timings.getStagedRectScaleXInterpolator(), + final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, + clampToProgress(timings.getStagedRectScaleXInterpolator(), timings.getStagedRectSlideStartOffset(), timings.getStagedRectSlideEndOffset())); - final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY, 0, - animDuration, clampToProgress(timings.getStagedRectScaleYInterpolator(), + final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY, + clampToProgress(timings.getStagedRectScaleYInterpolator(), timings.getStagedRectSlideStartOffset(), timings.getStagedRectSlideEndOffset())); @Override