From 05e46ca8f3d24cf333e745c8cebb420b2996e9ea Mon Sep 17 00:00:00 2001 From: Winson Date: Fri, 5 Feb 2016 15:40:29 -0800 Subject: [PATCH] Updating paging animation to spec. - To handle the specific animation spec, we just animate the views manually instead of animating the stack scroll (like how we do when swiping to dismiss) - Fixing a regression in settings the initial focused index when alt-tabbing - Minor tweak to make the front most task smaller when in the initial non-paging mode Change-Id: Ic5fd54500fd8ce8284c7aaeddb102b2291bcecac Signed-off-by: Winson --- .../recents/RecentsActivityLaunchState.java | 5 +- .../android/systemui/recents/misc/Utilities.java | 2 + .../recents/views/TaskStackAnimationHelper.java | 99 ++++++++++++++++++++++ .../recents/views/TaskStackLayoutAlgorithm.java | 2 +- .../systemui/recents/views/TaskStackView.java | 79 +++++++++-------- .../recents/views/TaskStackViewScroller.java | 32 ++++--- .../recents/views/TaskStackViewTouchHandler.java | 6 +- .../android/systemui/recents/views/ViewPool.java | 8 +- 8 files changed, 169 insertions(+), 64 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java index f7ebd945f761..82e786140e77 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java @@ -66,8 +66,9 @@ public class RecentsActivityLaunchState { */ public int getInitialFocusTaskIndex(int numTasks) { RecentsDebugFlags debugFlags = Recents.getDebugFlags(); + RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); if (launchedFromAppWithThumbnail) { - if (debugFlags.isFastToggleRecentsEnabled()) { + if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) { // If fast toggling, focus the front most task so that the next tap will focus the // N-1 task return numTasks - 1; @@ -76,7 +77,7 @@ public class RecentsActivityLaunchState { // If coming from another app, focus the next task return numTasks - 2; } else { - if (debugFlags.isFastToggleRecentsEnabled()) { + if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) { // If fast toggling, defer focusing until the next tap (which will automatically // focus the front most task) return -1; diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java index 4deea542610a..52043f400bd7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java @@ -62,6 +62,8 @@ public class Utilities { } }; + public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator(); + /** * @return the first parent walking up the view hierarchy that has the given class type. * diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java index 7eaa1930f6a6..76972d75d9f0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java @@ -23,6 +23,7 @@ import android.content.res.Resources; import android.graphics.Path; import android.graphics.RectF; import android.view.View; +import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import com.android.systemui.Interpolators; @@ -34,6 +35,7 @@ import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; +import java.util.ArrayList; import java.util.List; /** @@ -81,9 +83,18 @@ public class TaskStackAnimationHelper { private static final PathInterpolator EXIT_TO_HOME_ALPHA_INTERPOLATOR = new PathInterpolator(0.4f, 0, 1f, 1f); + private static final PathInterpolator FOCUS_NEXT_TASK_INTERPOLATOR = + new PathInterpolator(0.4f, 0, 0, 1f); + private static final PathInterpolator FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR = + new PathInterpolator(0, 0, 0, 1f); + private static final PathInterpolator FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR = + new PathInterpolator(0.4f, 0, 0.2f, 1f); + private TaskStackView mStackView; private TaskViewTransform mTmpTransform = new TaskViewTransform(); + private ArrayList mTmpCurrentTaskTransforms = new ArrayList<>(); + private ArrayList mTmpFinalTaskTransforms = new ArrayList<>(); public TaskStackAnimationHelper(Context context, TaskStackView stackView) { mStackView = stackView; @@ -418,4 +429,92 @@ public class TaskStackAnimationHelper { mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); } } + + /** + * Starts the animation to focus the next {@link TaskView} when paging through recents. + * + * @return whether or not this will trigger a scroll in the stack + */ + public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask, + boolean requestViewFocus) { + TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); + TaskStackViewScroller stackScroller = mStackView.getScroller(); + TaskStack stack = mStackView.getStack(); + + final float newScroll = stackLayout.getStackScrollForTask(newFocusedTask); + boolean willScrollToFront = newScroll > stackScroller.getStackScroll(); + boolean willScroll = Float.compare(newScroll, stackScroller.getStackScroll()) != 0; + + // Get the current set of task transforms + ArrayList stackTasks = stack.getStackTasks(); + mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms); + + // Pick up the newly visible views after the scroll + mStackView.bindVisibleTaskViews(newScroll); + + // Update the internal state + stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED); + stackScroller.setStackScroll(newScroll, null /* animation */); + mStackView.cancelDeferredTaskViewLayoutAnimation(); + + // Get the final set of task transforms + mStackView.getLayoutTaskTransforms(newScroll, stackTasks, mTmpFinalTaskTransforms); + + // Focus the task view + TaskView newFocusedTaskView = mStackView.getChildViewForTask(newFocusedTask); + newFocusedTaskView.setFocusedState(true, requestViewFocus); + + // Setup the end listener to return all the hidden views to the view pool after the + // focus animation + AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mStackView.bindVisibleTaskViews(newScroll); + } + }; + + List taskViews = mStackView.getTaskViews(); + int taskViewCount = taskViews.size(); + int newFocusTaskViewIndex = taskViews.indexOf(newFocusedTaskView); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); + Task task = tv.getTask(); + + if (mStackView.isIgnoredTask(task)) { + continue; + } + + int taskIndex = stackTasks.indexOf(task); + TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex); + TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex); + + // Update the task to the initial state (for the newly picked up tasks) + mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE); + + int duration; + Interpolator interpolator; + if (willScrollToFront) { + duration = Math.max(100, 100 + ((i - 1) * 50)); + interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR; + } else { + if (i < newFocusTaskViewIndex) { + duration = 150 + ((newFocusTaskViewIndex - i - 1) * 50); + interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR; + } else if (i > newFocusTaskViewIndex) { + duration = Math.max(100, 150 - ((i - newFocusTaskViewIndex - 1) * 50)); + interpolator = FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR; + } else { + duration = 200; + interpolator = FOCUS_NEXT_TASK_INTERPOLATOR; + } + } + + AnimationProps anim = new AnimationProps() + .setDuration(AnimationProps.BOUNDS, duration) + .setInterpolator(AnimationProps.BOUNDS, interpolator) + .setListener(endListener); + mStackView.updateTaskViewToTransform(tv, toTransform, anim); + } + return willScroll; + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index bd37c3bfd761..19ac1e7dd44d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -457,7 +457,7 @@ public class TaskStackLayoutAlgorithm { launchTaskIndex - 1)); } } else { - float offsetPct = (float) (mTaskRect.height() / 2) / mStackRect.height(); + float offsetPct = (float) (mTaskRect.height() / 3) / mStackRect.height(); float normX = mUnfocusedCurveInterpolator.getX(offsetPct); mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, launchTaskIndex - mUnfocusedRange.getAbsoluteX(normX))); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 59bc12099847..fb3515a26380 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -20,6 +20,8 @@ import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.ComponentName; @@ -41,6 +43,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; import android.widget.FrameLayout; import com.android.internal.logging.MetricsLogger; @@ -115,6 +119,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal private static final ArraySet EMPTY_TASK_SET = new ArraySet<>(); + LayoutInflater mInflater; TaskStack mStack; TaskStackLayoutAlgorithm mLayoutAlgorithm; TaskStackViewScroller mStackScroller; @@ -144,16 +149,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal boolean mScreenPinningEnabled; // The stable stack bounds are the full bounds that we were measured with from RecentsView - Rect mStableStackBounds = new Rect(); + private Rect mStableStackBounds = new Rect(); // The current stack bounds are dynamic and may change as the user drags and drops - Rect mStackBounds = new Rect(); + private Rect mStackBounds = new Rect(); - int[] mTmpVisibleRange = new int[2]; - Rect mTmpRect = new Rect(); - ArrayMap mTmpTaskViewMap = new ArrayMap<>(); - List mTmpTaskViews = new ArrayList<>(); - TaskViewTransform mTmpTransform = new TaskViewTransform(); - LayoutInflater mInflater; + private int[] mTmpVisibleRange = new int[2]; + private Rect mTmpRect = new Rect(); + private ArrayMap mTmpTaskViewMap = new ArrayMap<>(); + private List mTmpTaskViews = new ArrayList<>(); + private TaskViewTransform mTmpTransform = new TaskViewTransform(); // A convenience update listener to request updating clipping of tasks private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener = @@ -398,6 +402,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int frontMostVisibleIndex = -1; int backMostVisibleIndex = -1; boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0; + boolean targetScrollIsInFront = targetStackScroll > curStackScroll; // We can reuse the task transforms where possible to reduce object allocation Utilities.matchTaskListSize(tasks, taskTransforms); @@ -441,7 +446,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal frontMostVisibleIndex = i; } backMostVisibleIndex = i; - } else { + } else if (!targetScrollIsInFront) { if (backMostVisibleIndex != -1) { // We've reached the end of the visible range, so going down the rest of the // stack, we can just reset the transforms accordingly @@ -533,7 +538,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Skip the invisible non-freeform stack tasks - if (i > visibleStackRange[0] && !task.isFreeformTask()) { + if (!task.isFreeformTask() && !transform.visible) { continue; } @@ -673,12 +678,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal for (int i = tasks.size() - 1; i >= 0; i--) { Task task = tasks.get(i); TaskViewTransform transform = transformsOut.get(i); - mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null); + mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null, + true /* forceUpdate */); transform.visible = true; } } /** + * Cancels the next deferred task view layout. + */ + void cancelDeferredTaskViewLayoutAnimation() { + mDeferredTaskViewLayoutAnimation = null; + } + + /** * Cancels all {@link TaskView} animations. * * @see #cancelAllTaskViewAnimations(ArraySet) @@ -718,7 +731,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal TaskView frontTv = null; int clipBottom = 0; - if (mIgnoreTasks.contains(tv.getTask().key)) { + if (isIgnoredTask(tv.getTask())) { // For each of the ignore tasks, update the translationZ of its TaskView to be // between the translationZ of the tasks immediately underneath it if (prevVisibleTv != null) { @@ -806,15 +819,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** - * Sets the focused task to the provided (bounded taskIndex). + * Sets the focused task to the provided (bounded focusTaskIndex). * * @return whether or not the stack will scroll as a part of this focus change */ - private boolean setFocusedTask(int taskIndex, boolean scrollToTask, - final boolean requestViewFocus, final int timerIndicatorDuration) { + private boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask, + boolean requestViewFocus, int timerIndicatorDuration) { // Find the next task to focus int newFocusedTaskIndex = mStack.getTaskCount() > 0 ? - Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1; + Math.max(0, Math.min(mStack.getTaskCount() - 1, focusTaskIndex)) : -1; final Task newFocusedTask = (newFocusedTaskIndex != -1) ? mStack.getStackTasks().get(newFocusedTaskIndex) : null; @@ -832,7 +845,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } boolean willScroll = false; - mFocusedTask = newFocusedTask; if (newFocusedTask != null) { @@ -847,33 +859,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } - Runnable focusTaskRunnable = new Runnable() { - @Override - public void run() { - final TaskView tv = getChildViewForTask(newFocusedTask); - if (tv != null) { - tv.setFocusedState(true, requestViewFocus); - } - } - }; - if (scrollToTask) { // Cancel any running enter animations at this point when we scroll or change focus if (!mEnterAnimationComplete) { cancelAllTaskViewAnimations(); } - // TODO: Center the newly focused task view, only if not freeform - float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask); - if (Float.compare(newScroll, mStackScroller.getStackScroll()) != 0) { - mStackScroller.animateScroll(newScroll, focusTaskRunnable); - willScroll = true; - } else { - focusTaskRunnable.run(); - } - mLayoutAlgorithm.animateFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED); + willScroll = mAnimationHelper.startScrollToFocusedTaskAnimation(newFocusedTask, + requestViewFocus); } else { - focusTaskRunnable.run(); + // Focus the task view + TaskView newFocusedTaskView = getChildViewForTask(newFocusedTask); + if (newFocusedTaskView != null) { + newFocusedTaskView.setFocusedState(true, requestViewFocus); + } } } return willScroll; @@ -1278,7 +1277,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Task task = tasks.get(i); // Ignore deleting tasks - if (mIgnoreTasks.contains(task.key)) { + if (isIgnoredTask(task)) { if (i == tasks.size() - 1) { isFrontMostTask.value = true; } @@ -1392,7 +1391,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } @Override - public void prepareViewToEnterPool(TaskView tv) { + public void onReturnViewToPool(TaskView tv) { final Task task = tv.getTask(); // Report that this tasks's data is no longer being used @@ -1413,7 +1412,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } @Override - public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) { + public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) { // Find the index where this task should be placed in the stack int taskIndex = mStack.indexOfStackTask(task); int insertIndex = findTaskViewInsertIndex(task, taskIndex); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index c641d75c7bc8..d1bce55c324e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -191,21 +191,27 @@ public class TaskStackViewScroller { stopScroller(); stopBoundScrollAnimation(); - mFinalAnimatedScroll = newScroll; - mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll); - mScrollAnimator.setDuration(mContext.getResources().getInteger( - R.integer.recents_animate_task_stack_scroll_duration)); - mScrollAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - mScrollAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (postRunnable != null) { - postRunnable.run(); + if (Float.compare(mStackScrollP, newScroll) != 0) { + mFinalAnimatedScroll = newScroll; + mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll); + mScrollAnimator.setDuration(mContext.getResources().getInteger( + R.integer.recents_animate_task_stack_scroll_duration)); + mScrollAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + mScrollAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (postRunnable != null) { + postRunnable.run(); + } + mScrollAnimator.removeAllListeners(); } - mScrollAnimator.removeAllListeners(); + }); + mScrollAnimator.start(); + } else { + if (postRunnable != null) { + postRunnable.run(); } - }); - mScrollAnimator.start(); + } } /** Aborts any current stack scrolls */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index a7bc3dfb5dbb..5d1bb66f0dac 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -59,8 +59,6 @@ import java.util.List; class TaskStackViewTouchHandler implements SwipeHelper.Callback { private static final int INACTIVE_POINTER_ID = -1; - - private static final RectFEvaluator RECT_EVALUATOR = new RectFEvaluator(); private static final Interpolator STACK_TRANSFORM_INTERPOLATOR = new PathInterpolator(0.73f, 0.33f, 0.42f, 0.85f); @@ -545,8 +543,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mTmpTransform.copyFrom(fromTransform); // We only really need to interpolate the bounds, progress and translation - mTmpTransform.rect.set(RECT_EVALUATOR.evaluate(dismissFraction, fromTransform.rect, - toTransform.rect)); + mTmpTransform.rect.set(Utilities.RECTF_EVALUATOR.evaluate(dismissFraction, + fromTransform.rect, toTransform.rect)); mTmpTransform.p = fromTransform.p + (toTransform.p - fromTransform.p) * dismissFraction; mTmpTransform.translationZ = fromTransform.translationZ + (toTransform.translationZ - fromTransform.translationZ) * dismissFraction; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java index 31fbd3e15705..a287fe642002 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java @@ -29,8 +29,8 @@ public class ViewPool { /* An interface to the consumer of a view pool */ public interface ViewPoolConsumer { public V createView(Context context); - public void prepareViewToEnterPool(V v); - public void prepareViewToLeavePool(V v, T prepareData, boolean isNewView); + public void onReturnViewToPool(V v); + public void onPickUpViewFromPool(V v, T prepareData, boolean isNewView); public boolean hasPreferredData(V v, T preferredData); } @@ -46,7 +46,7 @@ public class ViewPool { /** Returns a view into the pool */ void returnViewToPool(V v) { - mViewCreator.prepareViewToEnterPool(v); + mViewCreator.onReturnViewToPool(v); mPool.push(v); } @@ -73,7 +73,7 @@ public class ViewPool { v = mPool.pop(); } } - mViewCreator.prepareViewToLeavePool(v, prepareData, isNewView); + mViewCreator.onPickUpViewFromPool(v, prepareData, isNewView); return v; } -- 2.11.0