diff --git a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java index 34f580b2bc..48b07a7144 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java @@ -15,7 +15,9 @@ */ package com.android.quickstep.util; -import android.animation.TimeInterpolator; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.PixelFormat; @@ -30,7 +32,7 @@ import com.android.quickstep.views.TaskView; public class TaskViewDrawable extends Drawable { - public static FloatProperty PROGRESS = + public static final FloatProperty PROGRESS = new FloatProperty("progress") { @Override public void setValue(TaskViewDrawable taskViewDrawable, float v) { @@ -43,8 +45,10 @@ public class TaskViewDrawable extends Drawable { } }; - private static final TimeInterpolator ICON_SIZE_INTERPOLATOR = - (t) -> (Math.max(t, 0.3f) - 0.3f) / 0.7f; + /** + * The progress at which we play the atomic icon scale animation. + */ + private static final float ICON_SCALE_THRESHOLD = 0.95f; private final RecentsView mParent; private final View mIconView; @@ -55,11 +59,15 @@ public class TaskViewDrawable extends Drawable { private final ClipAnimationHelper mClipAnimationHelper; private float mProgress = 1; + private boolean mPassedIconScaleThreshold; + private ValueAnimator mIconScaleAnimator; + private float mIconScale; public TaskViewDrawable(TaskView tv, RecentsView parent) { mParent = parent; mIconView = tv.getIconView(); mIconPos = new int[2]; + mIconScale = mIconView.getScaleX(); Utilities.getDescendantCoordRelativeToAncestor(mIconView, parent, mIconPos, true); mThumbnailView = tv.getThumbnail(); @@ -70,6 +78,37 @@ public class TaskViewDrawable extends Drawable { public void setProgress(float progress) { mProgress = progress; mParent.invalidate(); + boolean passedIconScaleThreshold = progress <= ICON_SCALE_THRESHOLD; + if (mPassedIconScaleThreshold != passedIconScaleThreshold) { + mPassedIconScaleThreshold = passedIconScaleThreshold; + animateIconScale(mPassedIconScaleThreshold ? 0 : 1); + } + } + + private void animateIconScale(float toScale) { + if (mIconScaleAnimator != null) { + mIconScaleAnimator.cancel(); + } + mIconScaleAnimator = ValueAnimator.ofFloat(mIconScale, toScale); + mIconScaleAnimator.addUpdateListener(valueAnimator -> { + mIconScale = (float) valueAnimator.getAnimatedValue(); + if (mProgress > ICON_SCALE_THRESHOLD) { + // Speed up the icon scale to ensure it is 1 when progress is 1. + float iconProgress = (mProgress - ICON_SCALE_THRESHOLD) / (1 - ICON_SCALE_THRESHOLD); + if (iconProgress > mIconScale) { + mIconScale = iconProgress; + } + } + invalidateSelf(); + }); + mIconScaleAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mIconScaleAnimator = null; + } + }); + mIconScaleAnimator.setDuration(TaskView.SCALE_ICON_DURATION); + mIconScaleAnimator.start(); } @Override @@ -81,8 +120,7 @@ public class TaskViewDrawable extends Drawable { canvas.save(); canvas.translate(mIconPos[0], mIconPos[1]); - float scale = ICON_SIZE_INTERPOLATOR.getInterpolation(mProgress); - canvas.scale(scale, scale, mIconView.getWidth() / 2, mIconView.getHeight() / 2); + canvas.scale(mIconScale, mIconScale, mIconView.getWidth() / 2, mIconView.getHeight() / 2); mIconView.draw(canvas); canvas.restore(); } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 82a5bdc98d..10e5c0a1fe 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -22,6 +22,7 @@ import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; +import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; @@ -960,15 +961,13 @@ public abstract class RecentsView extends PagedView impl if (currTask == null) { return; } - currTask.setScaleX(mAdjacentScale); - currTask.setScaleY(mAdjacentScale); + currTask.setZoomScale(mAdjacentScale); if (mCurrentPage - 1 >= 0) { TaskView adjacentTask = getPageAt(mCurrentPage - 1); float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask, mAdjacentScale, 0); - adjacentTask.setScaleX(scaleAndTranslation[0]); - adjacentTask.setScaleY(scaleAndTranslation[0]); + adjacentTask.setZoomScale(scaleAndTranslation[0]); adjacentTask.setTranslationX(-scaleAndTranslation[1]); adjacentTask.setTranslationY(scaleAndTranslation[2]); } @@ -976,8 +975,7 @@ public abstract class RecentsView extends PagedView impl TaskView adjacentTask = getPageAt(mCurrentPage + 1); float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask, mAdjacentScale, 0); - adjacentTask.setScaleX(scaleAndTranslation[0]); - adjacentTask.setScaleY(scaleAndTranslation[0]); + adjacentTask.setZoomScale(scaleAndTranslation[0]); adjacentTask.setTranslationX(scaleAndTranslation[1]); adjacentTask.setTranslationY(scaleAndTranslation[2]); } @@ -986,7 +984,7 @@ public abstract class RecentsView extends PagedView impl private float[] getAdjacentScaleAndTranslation(TaskView currTask, TaskView adjacentTask, float currTaskToScale, float currTaskToTranslationY) { float displacement = currTask.getWidth() * (currTaskToScale - currTask.getCurveScale()); - sTempFloatArray[0] = currTaskToScale * adjacentTask.getCurveScale(); + sTempFloatArray[0] = currTaskToScale; sTempFloatArray[1] = mIsRtl ? -displacement : displacement; sTempFloatArray[2] = currTaskToTranslationY; return sTempFloatArray; @@ -1127,13 +1125,15 @@ public abstract class RecentsView extends PagedView impl return anim; } - private ObjectAnimator createAnimForChild(View child, float[] toScaleAndTranslation) { - return ObjectAnimator.ofPropertyValuesHolder(child, + private Animator createAnimForChild(TaskView child, float[] toScaleAndTranslation) { + AnimatorSet anim = new AnimatorSet(); + anim.play(ObjectAnimator.ofFloat(child, TaskView.ZOOM_SCALE, toScaleAndTranslation[0])); + anim.play(ObjectAnimator.ofPropertyValuesHolder(child, new PropertyListBuilder() - .scale(child.getScaleX() * toScaleAndTranslation[0]) .translationX(toScaleAndTranslation[1]) .translationY(toScaleAndTranslation[2]) - .build()); + .build())); + return anim; } public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) { diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index b8b9196e7c..4f447b113a 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -30,14 +30,15 @@ import android.graphics.Outline; import android.os.Bundle; import android.os.Handler; import android.util.AttributeSet; +import android.util.FloatProperty; import android.util.Log; +import android.util.Property; import android.view.View; import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; -import android.widget.ImageView; - import android.widget.Toast; + import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; @@ -76,12 +77,26 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback */ private static final float EDGE_SCALE_DOWN_FACTOR = 0.03f; - private static final long SCALE_ICON_DURATION = 120; + public static final long SCALE_ICON_DURATION = 120; + + public static final Property ZOOM_SCALE = + new FloatProperty("zoomScale") { + @Override + public void setValue(TaskView taskView, float v) { + taskView.setZoomScale(v); + } + + @Override + public Float get(TaskView taskView) { + return taskView.mZoomScale; + } + }; private Task mTask; private TaskThumbnailView mSnapshotView; private IconView mIconView; private float mCurveScale; + private float mZoomScale; private float mCurveDimAlpha; private Animator mDimAlphaAnim; @@ -207,8 +222,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback } public void resetVisualProperties() { - setScaleX(1f); - setScaleY(1f); + setZoomScale(1); setTranslationX(0f); setTranslationY(0f); setTranslationZ(0); @@ -226,9 +240,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback mSnapshotView.setDimAlpha(mCurveDimAlpha); } - mCurveScale = getCurveScaleForCurveInterpolation(curveInterpolation); - setScaleX(mCurveScale); - setScaleY(mCurveScale); + setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation)); } @Override @@ -247,10 +259,26 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR; } + private void setCurveScale(float curveScale) { + mCurveScale = curveScale; + onScaleChanged(); + } + public float getCurveScale() { return mCurveScale; } + public void setZoomScale(float adjacentScale) { + mZoomScale = adjacentScale; + onScaleChanged(); + } + + private void onScaleChanged() { + float scale = mCurveScale * mZoomScale; + setScaleX(scale); + setScaleY(scale); + } + @Override public boolean hasOverlappingRendering() { // TODO: Clip-out the icon region from the thumbnail, since they are overlapping.