From f95e84a3852e1c85b2326b03431fd4b2ae20ed74 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 7 May 2019 16:56:12 -0700 Subject: [PATCH 1/5] Polish app => recents animation (1/N) As part of building out a better remote app to overview animation, we first separate out the logic so that we only do the default layout animation when coming from a Launcher state (i.e. from home or all apps) and only then, so coming from an app no longer leads to the tasks animating. We do this with a simple flag that indicates that recents will be using a remote animation. Bug: 132112131 Test: Go to app, go to recents. No layout animation. Test: Go to home, go to recents. Layout animation works as normal Change-Id: I30988d944571fd5317d0c9d13e2a99b167c1291b --- .../FallbackActivityControllerHelper.java | 1 + .../LauncherActivityControllerHelper.java | 2 ++ .../android/quickstep/views/IconRecentsView.java | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java index bba08a40af..d39a66c707 100644 --- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java +++ b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java @@ -45,6 +45,7 @@ public final class FallbackActivityControllerHelper extends @Override public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible, boolean animateActivity, Consumer callback) { + // TODO: Logic for setting remote animation if (activityVisible) { return (transitionLength) -> { }; } diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java index 40db7ddbce..d5950077de 100644 --- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -40,6 +40,7 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel boolean activityVisible, boolean animateActivity, Consumer callback) { LauncherState fromState = activity.getStateManager().getState(); + activity.getOverviewPanel().setUsingRemoteAnimation(true); //TODO: Implement this based off where the recents view needs to be for app => recents anim. return new AnimationFactory() { @Override @@ -87,6 +88,7 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel if (launcher == null) { return false; } + launcher.getOverviewPanel().setUsingRemoteAnimation(false); launcher.getUserEventDispatcher().logActionCommand( LauncherLogProto.Action.Command.RECENTS_BUTTON, getContainerType(), diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index 7225e572ba..c69e534581 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -125,6 +125,7 @@ public final class IconRecentsView extends FrameLayout implements Insettable { private View mEmptyView; private View mContentView; private boolean mTransitionedFromApp; + private boolean mUsingRemoteAnimation; private AnimatorSet mLayoutAnimation; private final ArraySet mLayingOutViews = new ArraySet<>(); private Rect mInsets; @@ -276,7 +277,9 @@ public final class IconRecentsView extends FrameLayout implements Insettable { // not be scrollable. mTaskLayoutManager.scrollToPositionWithOffset(TASKS_START_POSITION, 0 /* offset */); } - scheduleFadeInLayoutAnimation(); + if (!mUsingRemoteAnimation) { + scheduleFadeInLayoutAnimation(); + } // Load any task changes if (!mTaskLoader.needsToLoad()) { return; @@ -314,6 +317,17 @@ public final class IconRecentsView extends FrameLayout implements Insettable { mTransitionedFromApp = transitionedFromApp; } + /** + * Set whether we're using a custom remote animation. If so, we will not do the default layout + * animation when entering recents and instead wait for the remote app surface to be ready to + * use. + * + * @param usingRemoteAnimation true if doing a remote animation, false o/w + */ + public void setUsingRemoteAnimation(boolean usingRemoteAnimation) { + mUsingRemoteAnimation = usingRemoteAnimation; + } + /** * Handles input from the overview button. Launch the most recent task unless we just came from * the app. In that case, we launch the next most recent. From 1b4b960ff113794232e3a779ab01f4450f8e3ed9 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 7 May 2019 17:40:32 -0700 Subject: [PATCH 2/5] Polish app => recents animation (2/N) This CL moves the core "app to overview" animation logic into the main view as the newer animation would need to animate over several of the main view's children and so the animation logic needs knowledge of those children. Bug: 132112131 Test: Build and test, functions as before Change-Id: I9cbde55a582bee62e0a97e38c5fdf1d5841502db --- .../AppToOverviewAnimationProvider.java | 107 +----------------- .../quickstep/views/IconRecentsView.java | 103 +++++++++++++++++ 2 files changed, 106 insertions(+), 104 deletions(-) diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java index c228bb94fe..2dc25546d6 100644 --- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -15,30 +15,21 @@ */ package com.android.quickstep; -import static com.android.launcher3.anim.Interpolators.ACCEL_2; -import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; -import static com.android.quickstep.util.RemoteAnimationProvider.getLayer; +import static com.android.quickstep.views.IconRecentsView.REMOTE_APP_TO_OVERVIEW_DURATION; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; import android.animation.AnimatorSet; import android.animation.ValueAnimator; -import android.graphics.Matrix; -import android.graphics.Rect; import android.util.Log; import android.view.View; -import androidx.annotation.NonNull; - import com.android.launcher3.BaseDraggingActivity; -import com.android.quickstep.util.MultiValueUpdateListener; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteAnimationTargetSet; import com.android.quickstep.views.IconRecentsView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; /** * Provider for the atomic remote window animation from the app to the overview. @@ -47,9 +38,6 @@ import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat. */ final class AppToOverviewAnimationProvider implements RemoteAnimationProvider { - - private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50; - private static final long APP_SCALE_DOWN_DURATION = 400; private static final String TAG = "AppToOverviewAnimationProvider"; private final ActivityControlHelper mHelper; @@ -131,106 +119,17 @@ final class AppToOverviewAnimationProvider imple return anim; } - View thumbnailView = mRecentsView.getBottomThumbnailView(); - if (thumbnailView == null) { - // This can be null if there were previously 0 tasks and the recycler view has not had - // enough time to take in the data change, bind a new view, and lay out the new view. - // TODO: Have a fallback to animate to - if (Log.isLoggable(TAG, Log.WARN)) { - Log.w(TAG, "No thumbnail view for running task. Using stub animation."); - } - anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration())); - return anim; - } - - playAppScaleDownAnim(anim, closingAppTarget, recentsTarget, thumbnailView); + mRecentsView.playAppScaleDownAnim(anim, closingAppTarget, recentsTarget); return anim; } - /** - * Animate a closing app to scale down to the location of the thumbnail view in recents. - * - * @param anim animator set - * @param appTarget the app surface thats closing - * @param recentsTarget the surface containing recents - * @param thumbnailView the thumbnail view to animate to - */ - private void playAppScaleDownAnim(@NonNull AnimatorSet anim, - @NonNull RemoteAnimationTargetCompat appTarget, - @NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) { - - // Identify where the entering remote app should animate to. - Rect endRect = new Rect(); - thumbnailView.getGlobalVisibleRect(endRect); - - Rect appBounds = appTarget.sourceContainerBounds; - - ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1); - valueAnimator.setDuration(APP_SCALE_DOWN_DURATION); - - SyncRtSurfaceTransactionApplierCompat surfaceApplier = - new SyncRtSurfaceTransactionApplierCompat(thumbnailView); - - // Keep recents visible throughout the animation. - SurfaceParams[] params = new SurfaceParams[2]; - // Closing app should stay on top. - int boostedMode = MODE_CLOSING; - params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */, - null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */); - - valueAnimator.addUpdateListener(new MultiValueUpdateListener() { - private final FloatProp mScaleX; - private final FloatProp mScaleY; - private final FloatProp mTranslationX; - private final FloatProp mTranslationY; - private final FloatProp mAlpha; - - { - // Scale down and move to view location. - float endScaleX = ((float) endRect.width()) / appBounds.width(); - mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); - float endScaleY = ((float) endRect.height()) / appBounds.height(); - mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); - float endTranslationX = endRect.left - - (appBounds.width() - thumbnailView.getWidth()) / 2.0f; - mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); - float endTranslationY = endRect.top - - (appBounds.height() - thumbnailView.getHeight()) / 2.0f; - mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); - - // Fade out quietly near the end to be replaced by the real view. - mAlpha = new FloatProp(1.0f, 0, - APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION, - APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2); - } - - @Override - public void onUpdate(float percent) { - Matrix m = new Matrix(); - m.setScale(mScaleX.value, mScaleY.value, - appBounds.width() / 2.0f, appBounds.height() / 2.0f); - m.postTranslate(mTranslationX.value, mTranslationY.value); - - params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m, - null /* windowCrop */, getLayer(appTarget, boostedMode), - 0 /* cornerRadius */); - surfaceApplier.scheduleApply(params); - } - }); - anim.play(valueAnimator); - } - /** * Get duration of animation from app to overview. * * @return duration of animation */ long getRecentsLaunchDuration() { - return APP_SCALE_DOWN_DURATION; + return REMOTE_APP_TO_OVERVIEW_DURATION; } } diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index c69e534581..89d248cd3a 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -19,10 +19,14 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL; +import static com.android.launcher3.anim.Interpolators.ACCEL_2; +import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT; import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL; import static com.android.quickstep.TaskAdapter.ITEM_TYPE_TASK; import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION; +import static com.android.quickstep.util.RemoteAnimationProvider.getLayer; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -32,6 +36,7 @@ import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; +import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.ArraySet; @@ -64,7 +69,11 @@ import com.android.quickstep.TaskAdapter; import com.android.quickstep.TaskHolder; import com.android.quickstep.TaskListLoader; import com.android.quickstep.TaskSwipeCallback; +import com.android.quickstep.util.MultiValueUpdateListener; import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; import java.util.ArrayList; import java.util.List; @@ -102,6 +111,11 @@ public final class IconRecentsView extends FrameLayout implements Insettable { private static final float ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO = .25f; private static final long CLEAR_ALL_FADE_DELAY = 120; + private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50; + private static final long APP_SCALE_DOWN_DURATION = 400; + + public static final long REMOTE_APP_TO_OVERVIEW_DURATION = APP_SCALE_DOWN_DURATION; + /** * A ratio representing the view's relative placement within its padded space. For example, 0 * is top aligned and 0.5 is centered vertically. @@ -565,6 +579,95 @@ public final class IconRecentsView extends FrameLayout implements Insettable { mLayoutAnimation.start(); } + /** + * Animate a closing app to scale down to the location of the thumbnail view in recents. + * + * @param anim animator set + * @param appTarget the app surface thats closing + * @param recentsTarget the surface containing recents + */ + public void playAppScaleDownAnim(@NonNull AnimatorSet anim, + @NonNull RemoteAnimationTargetCompat appTarget, + @NonNull RemoteAnimationTargetCompat recentsTarget) { + + View thumbnailView = getBottomThumbnailView(); + + if (thumbnailView == null) { + // This can be null if there were previously 0 tasks and the recycler view has not had + // enough time to take in the data change, bind a new view, and lay out the new view. + // TODO: Have a fallback to animate to + anim.play(ValueAnimator.ofInt(0, 1).setDuration(REMOTE_APP_TO_OVERVIEW_DURATION)); + } + + // Identify where the entering remote app should animate to. + Rect endRect = new Rect(); + thumbnailView.getGlobalVisibleRect(endRect); + + Rect appBounds = appTarget.sourceContainerBounds; + + ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1); + valueAnimator.setDuration(APP_SCALE_DOWN_DURATION); + + SyncRtSurfaceTransactionApplierCompat surfaceApplier = + new SyncRtSurfaceTransactionApplierCompat(thumbnailView); + + // Keep recents visible throughout the animation. + SurfaceParams[] params = new SurfaceParams[2]; + // Closing app should stay on top. + int boostedMode = MODE_CLOSING; + params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */, + null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */); + + valueAnimator.addUpdateListener(new MultiValueUpdateListener() { + private final FloatProp mScaleX; + private final FloatProp mScaleY; + private final FloatProp mTranslationX; + private final FloatProp mTranslationY; + private final FloatProp mAlpha; + + { + // Scale down and move to view location. + float endScaleX = ((float) endRect.width()) / appBounds.width(); + mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + float endScaleY = ((float) endRect.height()) / appBounds.height(); + mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + float endTranslationX = endRect.left - + (appBounds.width() - thumbnailView.getWidth()) / 2.0f; + mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + float endTranslationY = endRect.top - + (appBounds.height() - thumbnailView.getHeight()) / 2.0f; + mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + + // Fade out quietly near the end to be replaced by the real view. + mAlpha = new FloatProp(1.0f, 0, + APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION, + APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2); + } + + @Override + public void onUpdate(float percent) { + Matrix m = new Matrix(); + m.setScale(mScaleX.value, mScaleY.value, + appBounds.width() / 2.0f, appBounds.height() / 2.0f); + m.postTranslate(mTranslationX.value, mTranslationY.value); + + params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m, + null /* windowCrop */, getLayer(appTarget, boostedMode), + 0 /* cornerRadius */); + surfaceApplier.scheduleApply(params); + } + }); + anim.play(valueAnimator); + } + + public long getAppToOverviewAnimationDuration() { + return APP_SCALE_DOWN_DURATION; + } + @Override public void setInsets(Rect insets) { mInsets = insets; From e590014834f3f4704bf7801555ff6c4bb19606b2 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 3 May 2019 13:14:40 -0700 Subject: [PATCH 3/5] Polish app => recents animation (3/N) This CL takes the current scale down animation and updates its interpolators and durations to match the UX spec. In addition, we move the core logic to a helper method as the whole remote animation will have other animations as well in the future. Bug: 132112131 Test: Manual Change-Id: I68b1d623b1b2c980dc34e41bbb5460cc021b5887 --- .../AppToOverviewAnimationProvider.java | 3 +- .../quickstep/views/IconRecentsView.java | 78 +++++++++++-------- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 2dc25546d6..62e40d1947 100644 --- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -23,7 +23,6 @@ import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MOD import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.util.Log; -import android.view.View; import com.android.launcher3.BaseDraggingActivity; import com.android.quickstep.util.RemoteAnimationProvider; @@ -119,7 +118,7 @@ final class AppToOverviewAnimationProvider imple return anim; } - mRecentsView.playAppScaleDownAnim(anim, closingAppTarget, recentsTarget); + mRecentsView.playRemoteAppToRecentsAnimation(anim, closingAppTarget, recentsTarget); return anim; } diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index 89d248cd3a..58c6709b40 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -20,7 +20,6 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; -import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT; import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL; import static com.android.quickstep.TaskAdapter.ITEM_TYPE_TASK; @@ -45,6 +44,7 @@ import android.util.FloatProperty; import android.view.View; import android.view.ViewDebug; import android.view.ViewTreeObserver; +import android.view.animation.PathInterpolator; import android.widget.FrameLayout; import androidx.annotation.NonNull; @@ -111,10 +111,15 @@ public final class IconRecentsView extends FrameLayout implements Insettable { private static final float ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO = .25f; private static final long CLEAR_ALL_FADE_DELAY = 120; - private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50; - private static final long APP_SCALE_DOWN_DURATION = 400; + private static final long REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION = 300; - public static final long REMOTE_APP_TO_OVERVIEW_DURATION = APP_SCALE_DOWN_DURATION; + private static final PathInterpolator FAST_OUT_SLOW_IN_1 = + new PathInterpolator(.4f, 0f, 0f, 1f); + private static final PathInterpolator FAST_OUT_SLOW_IN_2 = + new PathInterpolator(.5f, 0f, 0f, 1f); + + public static final long REMOTE_APP_TO_OVERVIEW_DURATION = + REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION; /** * A ratio representing the view's relative placement within its padded space. For example, 0 @@ -397,7 +402,7 @@ public final class IconRecentsView extends FrameLayout implements Insettable { * * @return the thumbnail view if laid out */ - public @Nullable View getBottomThumbnailView() { + private @Nullable View getBottomThumbnailView() { ArrayList taskViews = getTaskViews(); if (taskViews.isEmpty()) { return null; @@ -580,18 +585,17 @@ public final class IconRecentsView extends FrameLayout implements Insettable { } /** - * Animate a closing app to scale down to the location of the thumbnail view in recents. + * Play remote animation from app to recents. This should scale the currently closing app down + * to the recents thumbnail. * * @param anim animator set * @param appTarget the app surface thats closing * @param recentsTarget the surface containing recents */ - public void playAppScaleDownAnim(@NonNull AnimatorSet anim, + public void playRemoteAppToRecentsAnimation(@NonNull AnimatorSet anim, @NonNull RemoteAnimationTargetCompat appTarget, @NonNull RemoteAnimationTargetCompat recentsTarget) { - View thumbnailView = getBottomThumbnailView(); - if (thumbnailView == null) { // This can be null if there were previously 0 tasks and the recycler view has not had // enough time to take in the data change, bind a new view, and lay out the new view. @@ -599,15 +603,28 @@ public final class IconRecentsView extends FrameLayout implements Insettable { anim.play(ValueAnimator.ofInt(0, 1).setDuration(REMOTE_APP_TO_OVERVIEW_DURATION)); } + // TODO: Play other animations in app => recents + playRemoteAppScaleDownAnim(anim, appTarget, recentsTarget, thumbnailView); + } + + /** + * Play the scale down animation for the remote app to recents animation where the app surface + * scales down to where the thumbnail is. + * + * @param anim animator set to play on + * @param appTarget closing app target + * @param recentsTarget opening recents target + * @param thumbnailView thumbnail view to animate to + */ + private void playRemoteAppScaleDownAnim(@NonNull AnimatorSet anim, + @NonNull RemoteAnimationTargetCompat appTarget, + @NonNull RemoteAnimationTargetCompat recentsTarget, + @NonNull View thumbnailView) { // Identify where the entering remote app should animate to. Rect endRect = new Rect(); thumbnailView.getGlobalVisibleRect(endRect); - Rect appBounds = appTarget.sourceContainerBounds; - ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1); - valueAnimator.setDuration(APP_SCALE_DOWN_DURATION); - SyncRtSurfaceTransactionApplierCompat surfaceApplier = new SyncRtSurfaceTransactionApplierCompat(thumbnailView); @@ -618,7 +635,9 @@ public final class IconRecentsView extends FrameLayout implements Insettable { params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */, null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */); - valueAnimator.addUpdateListener(new MultiValueUpdateListener() { + ValueAnimator remoteAppAnim = ValueAnimator.ofInt(0, 1); + remoteAppAnim.setDuration(REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION); + remoteAppAnim.addUpdateListener(new MultiValueUpdateListener() { private final FloatProp mScaleX; private final FloatProp mScaleY; private final FloatProp mTranslationX; @@ -628,30 +647,27 @@ public final class IconRecentsView extends FrameLayout implements Insettable { { // Scale down and move to view location. float endScaleX = ((float) endRect.width()) / appBounds.width(); - mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); + mScaleX = new FloatProp(1f, endScaleX, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, + FAST_OUT_SLOW_IN_1); float endScaleY = ((float) endRect.height()) / appBounds.height(); - mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); + mScaleY = new FloatProp(1f, endScaleY, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, + FAST_OUT_SLOW_IN_1); float endTranslationX = endRect.left - (appBounds.width() - thumbnailView.getWidth()) / 2.0f; - mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); + mTranslationX = new FloatProp(0, endTranslationX, 0, + REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_1); float endTranslationY = endRect.top - (appBounds.height() - thumbnailView.getHeight()) / 2.0f; - mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); - - // Fade out quietly near the end to be replaced by the real view. - mAlpha = new FloatProp(1.0f, 0, - APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION, - APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2); + mTranslationY = new FloatProp(0, endTranslationY, 0, + REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_2); + mAlpha = new FloatProp(1.0f, 0, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, + ACCEL_2); } @Override public void onUpdate(float percent) { Matrix m = new Matrix(); - m.setScale(mScaleX.value, mScaleY.value, + m.preScale(mScaleX.value, mScaleY.value, appBounds.width() / 2.0f, appBounds.height() / 2.0f); m.postTranslate(mTranslationX.value, mTranslationY.value); @@ -661,11 +677,7 @@ public final class IconRecentsView extends FrameLayout implements Insettable { surfaceApplier.scheduleApply(params); } }); - anim.play(valueAnimator); - } - - public long getAppToOverviewAnimationDuration() { - return APP_SCALE_DOWN_DURATION; + anim.play(remoteAppAnim); } @Override From 07e12ee573b852487120a818cf271503f84b441e Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 3 May 2019 13:14:40 -0700 Subject: [PATCH 4/5] Polish app => recents animation (4/5) Add the vertical ease in component to the app => recents animation. In addition to animating over all task views, this should also animate over the closing app surface so that the surface movement matches the thumbnail as it moves to cover it. As a result, we take in the transformation matrix thats passed in to the app surface and apply the vertical motion to it. Bug: 132112131 Test: Manual Change-Id: I77c6716082af6ee722b96a2c362e16030cec8b8b --- .../quickstep/views/IconRecentsView.java | 84 ++++++++++++++----- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index 58c6709b40..4c91a59661 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -23,6 +23,7 @@ import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT; import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL; import static com.android.quickstep.TaskAdapter.ITEM_TYPE_TASK; +import static com.android.quickstep.TaskAdapter.MAX_TASKS_TO_DISPLAY; import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION; import static com.android.quickstep.util.RemoteAnimationProvider.getLayer; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; @@ -112,6 +113,7 @@ public final class IconRecentsView extends FrameLayout implements Insettable { private static final long CLEAR_ALL_FADE_DELAY = 120; private static final long REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION = 300; + private static final long REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION = 400; private static final PathInterpolator FAST_OUT_SLOW_IN_1 = new PathInterpolator(.4f, 0f, 0f, 1f); @@ -119,7 +121,7 @@ public final class IconRecentsView extends FrameLayout implements Insettable { new PathInterpolator(.5f, 0f, 0f, 1f); public static final long REMOTE_APP_TO_OVERVIEW_DURATION = - REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION; + REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION; /** * A ratio representing the view's relative placement within its padded space. For example, 0 @@ -398,17 +400,19 @@ public final class IconRecentsView extends FrameLayout implements Insettable { } /** - * Get the bottom most thumbnail view to animate to. + * Get the bottom most task view to animate to. * - * @return the thumbnail view if laid out + * @return the task view */ - private @Nullable View getBottomThumbnailView() { - ArrayList taskViews = getTaskViews(); - if (taskViews.isEmpty()) { - return null; + private @Nullable TaskItemView getBottomTaskView() { + int childCount = mTaskRecyclerView.getChildCount(); + for (int i = 0; i < childCount; i++) { + View view = mTaskRecyclerView.getChildAt(i); + if (mTaskRecyclerView.getChildViewHolder(view).getItemViewType() == ITEM_TYPE_TASK) { + return (TaskItemView) view; + } } - TaskItemView view = taskViews.get(0); - return view.getThumbnailView(); + return null; } /** @@ -595,16 +599,55 @@ public final class IconRecentsView extends FrameLayout implements Insettable { public void playRemoteAppToRecentsAnimation(@NonNull AnimatorSet anim, @NonNull RemoteAnimationTargetCompat appTarget, @NonNull RemoteAnimationTargetCompat recentsTarget) { - View thumbnailView = getBottomThumbnailView(); - if (thumbnailView == null) { + TaskItemView bottomView = getBottomTaskView(); + if (bottomView == null) { // This can be null if there were previously 0 tasks and the recycler view has not had // enough time to take in the data change, bind a new view, and lay out the new view. // TODO: Have a fallback to animate to anim.play(ValueAnimator.ofInt(0, 1).setDuration(REMOTE_APP_TO_OVERVIEW_DURATION)); } + final Matrix appMatrix = new Matrix(); + playRemoteTransYAnim(anim, appMatrix); + playRemoteAppScaleDownAnim(anim, appMatrix, appTarget, recentsTarget, + bottomView.getThumbnailView()); + } - // TODO: Play other animations in app => recents - playRemoteAppScaleDownAnim(anim, appTarget, recentsTarget, thumbnailView); + /** + * Play translation Y animation for the remote app to recents animation. Animates over all task + * views as well as the closing app, easing them into their final vertical positions. + * + * @param anim animator set to play on + * @param appMatrix transformation matrix for the closing app surface + */ + private void playRemoteTransYAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix) { + final ArrayList views = getTaskViews(); + + // Start Y translation from about halfway through the tasks list to the bottom thumbnail. + float taskHeight = getResources().getDimension(R.dimen.task_item_height); + float totalTransY = -(MAX_TASKS_TO_DISPLAY / 2.0f - 1) * taskHeight; + for (int i = 0, size = views.size(); i < size; i++) { + views.get(i).setTranslationY(totalTransY); + } + + ValueAnimator transYAnim = ValueAnimator.ofFloat(totalTransY, 0); + transYAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION); + transYAnim.setInterpolator(FAST_OUT_SLOW_IN_2); + transYAnim.addUpdateListener(valueAnimator -> { + float transY = (float) valueAnimator.getAnimatedValue(); + for (int i = 0, size = views.size(); i < size; i++) { + views.get(i).setTranslationY(transY); + } + appMatrix.postTranslate(0, transY - totalTransY); + }); + transYAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + for (int i = 0, size = views.size(); i < size; i++) { + views.get(i).setTranslationY(0); + } + } + }); + anim.play(transYAnim); } /** @@ -612,11 +655,12 @@ public final class IconRecentsView extends FrameLayout implements Insettable { * scales down to where the thumbnail is. * * @param anim animator set to play on + * @param appMatrix transformation matrix for the app surface * @param appTarget closing app target * @param recentsTarget opening recents target * @param thumbnailView thumbnail view to animate to */ - private void playRemoteAppScaleDownAnim(@NonNull AnimatorSet anim, + private void playRemoteAppScaleDownAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix, @NonNull RemoteAnimationTargetCompat appTarget, @NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) { @@ -626,7 +670,7 @@ public final class IconRecentsView extends FrameLayout implements Insettable { Rect appBounds = appTarget.sourceContainerBounds; SyncRtSurfaceTransactionApplierCompat surfaceApplier = - new SyncRtSurfaceTransactionApplierCompat(thumbnailView); + new SyncRtSurfaceTransactionApplierCompat(this); // Keep recents visible throughout the animation. SurfaceParams[] params = new SurfaceParams[2]; @@ -636,7 +680,7 @@ public final class IconRecentsView extends FrameLayout implements Insettable { null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */); ValueAnimator remoteAppAnim = ValueAnimator.ofInt(0, 1); - remoteAppAnim.setDuration(REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION); + remoteAppAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION); remoteAppAnim.addUpdateListener(new MultiValueUpdateListener() { private final FloatProp mScaleX; private final FloatProp mScaleY; @@ -666,15 +710,15 @@ public final class IconRecentsView extends FrameLayout implements Insettable { @Override public void onUpdate(float percent) { - Matrix m = new Matrix(); - m.preScale(mScaleX.value, mScaleY.value, + appMatrix.preScale(mScaleX.value, mScaleY.value, appBounds.width() / 2.0f, appBounds.height() / 2.0f); - m.postTranslate(mTranslationX.value, mTranslationY.value); + appMatrix.postTranslate(mTranslationX.value, mTranslationY.value); - params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m, + params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, appMatrix, null /* windowCrop */, getLayer(appTarget, boostedMode), 0 /* cornerRadius */); surfaceApplier.scheduleApply(params); + appMatrix.reset(); } }); anim.play(remoteAppAnim); From 5c0ee7ca370dcd2f47f4a557622f23332109a023 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 3 May 2019 13:14:40 -0700 Subject: [PATCH 5/5] Polish app => recents animation (5/5) Add the part of the animation where task views fade in from bottom to top. For the app animating in, we only fade in the label and the icon as the snapshot needs to be visible as the app window scales down to it. Bug: 132112131 Test: Manual test animation Change-Id: Ia3fb98fde0b14fa4f72b53a1941cc2ee6b9f2294 --- .../quickstep/views/IconRecentsView.java | 69 +++++++++++++++++++ .../android/quickstep/views/TaskItemView.java | 8 +++ 2 files changed, 77 insertions(+) diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index 4c91a59661..4413cc95be 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -50,6 +50,7 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; @@ -114,11 +115,16 @@ public final class IconRecentsView extends FrameLayout implements Insettable { private static final long REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION = 300; private static final long REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION = 400; + private static final long REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY = 200; + private static final long REMOTE_TO_RECENTS_ITEM_FADE_DURATION = 217; + private static final long REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY = 33; private static final PathInterpolator FAST_OUT_SLOW_IN_1 = new PathInterpolator(.4f, 0f, 0f, 1f); private static final PathInterpolator FAST_OUT_SLOW_IN_2 = new PathInterpolator(.5f, 0f, 0f, 1f); + private static final LinearOutSlowInInterpolator OUT_SLOW_IN = + new LinearOutSlowInInterpolator(); public static final long REMOTE_APP_TO_OVERVIEW_DURATION = REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION; @@ -610,6 +616,7 @@ public final class IconRecentsView extends FrameLayout implements Insettable { playRemoteTransYAnim(anim, appMatrix); playRemoteAppScaleDownAnim(anim, appMatrix, appTarget, recentsTarget, bottomView.getThumbnailView()); + playRemoteTaskListFadeIn(anim, bottomView); } /** @@ -724,6 +731,68 @@ public final class IconRecentsView extends FrameLayout implements Insettable { anim.play(remoteAppAnim); } + /** + * Play task list fade in animation as part of remote app to recents animation. This animation + * ensures that the task views in the recents list fade in from bottom to top. + * + * @param anim animator set to play on + * @param appTaskView the task view associated with the remote app closing + */ + private void playRemoteTaskListFadeIn(@NonNull AnimatorSet anim, + @NonNull TaskItemView appTaskView) { + long delay = REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY; + int childCount = mTaskRecyclerView.getChildCount(); + for (int i = 0; i < childCount; i++) { + ValueAnimator fadeAnim = ValueAnimator.ofFloat(0, 1.0f); + fadeAnim.setDuration(REMOTE_TO_RECENTS_ITEM_FADE_DURATION).setInterpolator(OUT_SLOW_IN); + fadeAnim.setStartDelay(delay); + View view = mTaskRecyclerView.getChildAt(i); + if (Objects.equals(view, appTaskView)) { + // Only animate icon and text for the view with snapshot animating in + final View icon = appTaskView.getIconView(); + final View label = appTaskView.getLabelView(); + + icon.setAlpha(0.0f); + label.setAlpha(0.0f); + + fadeAnim.addUpdateListener(alphaVal -> { + float val = alphaVal.getAnimatedFraction(); + + icon.setAlpha(val); + label.setAlpha(val); + }); + fadeAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + icon.setAlpha(1.0f); + label.setAlpha(1.0f); + } + }); + } else { + // Otherwise, fade in the entire view. + view.setAlpha(0.0f); + fadeAnim.addUpdateListener(alphaVal -> { + float val = alphaVal.getAnimatedFraction(); + view.setAlpha(val); + }); + fadeAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setAlpha(1.0f); + } + }); + } + anim.play(fadeAnim); + + int itemType = mTaskRecyclerView.getChildViewHolder(view).getItemViewType(); + if (itemType == ITEM_TYPE_CLEAR_ALL) { + // Don't add delay. Clear all should animate at same time as next view. + continue; + } + delay += REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY; + } + } + @Override public void setInsets(Rect insets) { mInsets = insets; diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java index 6db8013224..f184ad06a0 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java @@ -137,6 +137,14 @@ public final class TaskItemView extends LinearLayout { return mThumbnailView; } + public View getIconView() { + return mIconView; + } + + public View getLabelView() { + return mLabelView; + } + /** * Start a new animation from the current task content to the specified new content. The caller * is responsible for the actual animation control via the property