Merge changes from topic "polish-app-recents-anim" into ub-launcher3-qt-dev

* changes:
  Polish app => recents animation (5/5)
  Polish app => recents animation (4/5)
  Polish app => recents animation (3/N)
  Polish app => recents animation (2/N)
  Polish app => recents animation (1/N)
This commit is contained in:
TreeHugger Robot
2019-05-10 00:07:18 +00:00
committed by Android (Google) Code Review
5 changed files with 265 additions and 114 deletions
@@ -15,30 +15,20 @@
*/
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 +37,6 @@ import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.
*/
final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> 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<T> mHelper;
@@ -131,106 +118,17 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> 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.playRemoteAppToRecentsAnimation(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;
}
}
@@ -45,6 +45,7 @@ public final class FallbackActivityControllerHelper extends
@Override
public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
// TODO: Logic for setting remote animation
if (activityVisible) {
return (transitionLength) -> { };
}
@@ -40,6 +40,7 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel
boolean activityVisible, boolean animateActivity,
Consumer<AnimatorPlaybackController> callback) {
LauncherState fromState = activity.getStateManager().getState();
activity.<IconRecentsView>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.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(false);
launcher.getUserEventDispatcher().logActionCommand(
LauncherLogProto.Action.Command.RECENTS_BUTTON,
getContainerType(),
@@ -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.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;
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;
@@ -40,10 +45,12 @@ 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;
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;
@@ -64,7 +71,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 +113,22 @@ 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 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;
/**
* A ratio representing the view's relative placement within its padded space. For example, 0
* is top aligned and 0.5 is centered vertically.
@@ -125,6 +152,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<View> mLayingOutViews = new ArraySet<>();
private Rect mInsets;
@@ -276,7 +304,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 +344,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.
@@ -365,17 +406,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
*/
public @Nullable View getBottomThumbnailView() {
ArrayList<TaskItemView> 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;
}
/**
@@ -581,6 +624,205 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
mLayoutAnimation.start();
}
/**
* 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 playRemoteAppToRecentsAnimation(@NonNull AnimatorSet anim,
@NonNull RemoteAnimationTargetCompat appTarget,
@NonNull RemoteAnimationTargetCompat recentsTarget) {
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());
playRemoteTaskListFadeIn(anim, bottomView);
}
/**
* 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<TaskItemView> 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);
}
/**
* 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 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, @NonNull Matrix appMatrix,
@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;
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
new SyncRtSurfaceTransactionApplierCompat(this);
// 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 remoteAppAnim = ValueAnimator.ofInt(0, 1);
remoteAppAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION);
remoteAppAnim.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, 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, 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,
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,
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) {
appMatrix.preScale(mScaleX.value, mScaleY.value,
appBounds.width() / 2.0f, appBounds.height() / 2.0f);
appMatrix.postTranslate(mTranslationX.value, mTranslationY.value);
params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, appMatrix,
null /* windowCrop */, getLayer(appTarget, boostedMode),
0 /* cornerRadius */);
surfaceApplier.scheduleApply(params);
appMatrix.reset();
}
});
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;
@@ -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