From f24f21695f5609d06402cf61e3500d408b99bdcb Mon Sep 17 00:00:00 2001 From: Winson Date: Tue, 5 Jan 2016 12:11:55 -0800 Subject: Refactoring and unifying TaskView animations. - Adding notion of a TaskViewAnimation to animate a TaskView to a specific TaskViewTransform - Refactoring task view enter/exit/launch/delete animations into a separate class so that we can improve them easier - Removing individual TaskView view property animations in favor of using the existing TaskStackView stack animation. This ensures that we don't have to add separate logic when animating TaskViews. It is all handled by the TaskStackView now. - Breaking down the TaskStackView synchronize method into binding TaskViews and updating them to transforms. This allows us to synchronously update in many cases and is cleaner than the many request* calls. Change-Id: Ib26793568a14e837e6782358155f21158a133992 --- packages/SystemUI/res/values/config.xml | 2 +- .../DismissRecentsToHomeAnimationStarted.java | 1 - .../recents/misc/ReferenceCountedTrigger.java | 16 +- .../android/systemui/recents/misc/Utilities.java | 9 + .../recents/model/RecentsTaskLoadPlan.java | 3 - .../com/android/systemui/recents/model/Task.java | 3 +- .../android/systemui/recents/model/TaskStack.java | 16 + .../recents/views/AnimateableViewBounds.java | 31 +- .../systemui/recents/views/RecentsView.java | 98 ++-- .../recents/views/RecentsViewTouchHandler.java | 5 - .../recents/views/TaskStackAnimationHelper.java | 398 +++++++++++++ .../recents/views/TaskStackLayoutAlgorithm.java | 48 +- .../systemui/recents/views/TaskStackView.java | 642 ++++++++------------- .../recents/views/TaskStackViewScroller.java | 17 +- .../recents/views/TaskStackViewTouchHandler.java | 1 + .../android/systemui/recents/views/TaskView.java | 528 ++++++----------- .../systemui/recents/views/TaskViewAnimation.java | 78 +++ .../systemui/recents/views/TaskViewHeader.java | 16 - .../systemui/recents/views/TaskViewTransform.java | 105 ++-- .../systemui/recents/views/ViewAnimation.java | 68 --- 20 files changed, 1086 insertions(+), 999 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java create mode 100644 packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java delete mode 100644 packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 40e8b5055591..955af823a44f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -172,7 +172,7 @@ 400 - 250 + 175 200 diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java index 992afb42a1ca..e7be85868da2 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java @@ -28,5 +28,4 @@ public class DismissRecentsToHomeAnimationStarted extends EventBus.AnimatedEvent public DismissRecentsToHomeAnimationStarted(boolean animated) { this.animated = animated; } - } diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java index 90eaca79c8a5..2637d8812357 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java @@ -18,8 +18,6 @@ package com.android.systemui.recents.misc; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.util.Log; import java.util.ArrayList; @@ -107,16 +105,20 @@ public class ReferenceCountedTrigger { mLastDecRunnables.clear(); } - /** Convenience method to decrement this trigger as a runnable. */ - public Runnable decrementAsRunnable() { - return mDecrementRunnable; - } - /** Convenience method to decrement this trigger as a animator listener. */ + /** + * Convenience method to decrement this trigger as a animator listener. This listener is + * guarded to prevent being called back multiple times, and will trigger a decrement once and + * only once. + */ public Animator.AnimatorListener decrementOnAnimationEnd() { return new AnimatorListenerAdapter() { + private boolean hasEnded; + @Override public void onAnimationEnd(Animator animation) { + if (hasEnded) return; decrement(); + hasEnded = true; } }; } 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 2bf2ccba77c7..086fb5854fdb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java @@ -87,6 +87,15 @@ public class Utilities { (1f - overlayAlpha) * Color.blue(overlayColor))); } + /** + * Cancels an animation. + */ + public static void cancelAnimation(Animator animator) { + if (animator != null) { + animator.cancel(); + } + } + /** * Cancels an animation ensuring that if it has listeners, onCancel and onEnd * are not called. diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index d6262ac3da44..d8dfce568629 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -25,7 +25,6 @@ import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; import android.util.ArraySet; -import android.util.Log; import com.android.systemui.Prefs; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsConfiguration; @@ -115,8 +114,6 @@ public class RecentsTaskLoadPlan { * - least-recent to most-recent freeform tasks */ public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) { - RecentsConfiguration config = Recents.getConfiguration(); - SystemServicesProxy ssp = Recents.getSystemServices(); Resources res = mContext.getResources(); ArrayList allTasks = new ArrayList<>(); if (mRawTasks == null) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index d030fc1a906e..f7e2b9db24af 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -230,8 +230,7 @@ public class Task { public void notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultApplicationIcon) { icon = defaultApplicationIcon; thumbnail = defaultThumbnail; - int callbackCount = mCallbacks.size(); - for (int i = 0; i < callbackCount; i++) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { mCallbacks.get(i).onTaskDataUnloaded(); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index 5e720cba36c7..bae478472a5d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -563,6 +563,22 @@ public class TaskStack { return mHistoryTaskList.getTasks(); } + /** + * Returns the set of "freeform" tasks in the stack. + */ + public ArrayList getFreeformTasks() { + ArrayList freeformTasks = new ArrayList<>(); + ArrayList tasks = mStackTaskList.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + if (task.isFreeformTask()) { + freeformTasks.add(task); + } + } + return freeformTasks; + } + /** * Computes a set of all the active and historical tasks ordered by their last active time. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java index c0b8a9d23223..b8bbf5165248 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java @@ -18,8 +18,6 @@ package com.android.systemui.recents.views; import android.graphics.Outline; import android.graphics.Rect; -import android.util.IntProperty; -import android.util.Property; import android.view.View; import android.view.ViewOutlineProvider; @@ -29,23 +27,11 @@ public class AnimateableViewBounds extends ViewOutlineProvider { View mSourceView; Rect mClipRect = new Rect(); Rect mClipBounds = new Rect(); + Rect mLastClipBounds = new Rect(); int mCornerRadius; float mAlpha = 1f; final float mMinAlpha = 0.25f; - public static final Property CLIP_BOTTOM = - new IntProperty("clipBottom") { - @Override - public void setValue(AnimateableViewBounds object, int clip) { - object.setClipBottom(clip, false /* force */); - } - - @Override - public Integer get(AnimateableViewBounds object) { - return object.getClipBottom(); - } - }; - public AnimateableViewBounds(View source, int cornerRadius) { mSourceView = source; mCornerRadius = cornerRadius; @@ -77,11 +63,9 @@ public class AnimateableViewBounds extends ViewOutlineProvider { } /** Sets the bottom clip. */ - public void setClipBottom(int bottom, boolean force) { - if (bottom != mClipRect.bottom || force) { - mClipRect.bottom = bottom; - updateClipBounds(); - } + public void setClipBottom(int bottom) { + mClipRect.bottom = bottom; + updateClipBounds(); } /** Returns the bottom clip. */ @@ -93,7 +77,10 @@ public class AnimateableViewBounds extends ViewOutlineProvider { mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top), mSourceView.getWidth() - Math.max(0, mClipRect.right), mSourceView.getHeight() - Math.max(0, mClipRect.bottom)); - mSourceView.setClipBounds(mClipBounds); - mSourceView.invalidateOutline(); + if (!mLastClipBounds.equals(mClipBounds)) { + mSourceView.setClipBounds(mClipBounds); + mSourceView.invalidateOutline(); + mLastClipBounds.set(mClipBounds); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 9e9956e0a3b9..c4e5267c2aaf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -16,6 +16,8 @@ package com.android.systemui.recents.views; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; @@ -59,6 +61,7 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import com.android.systemui.stackdivider.WindowManagerProxy; @@ -116,7 +119,6 @@ public class RecentsView extends FrameLayout { public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - Resources res = context.getResources(); setWillNotDraw(false); mHandler = new Handler(); mTransitionHelper = new RecentsTransitionHelper(getContext(), mHandler); @@ -494,23 +496,25 @@ public class RecentsView extends FrameLayout { // Handle the case where we drop onto a dock region if (event.dropTarget instanceof TaskStack.DockState) { - final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; - - // Remove the task after it is docked - event.taskView.animate() - .alpha(0f) - .setDuration(150) - .setInterpolator(mFastOutLinearInInterpolator) - .setUpdateListener(null) - .setListener(null) - .withEndAction(new Runnable() { - @Override - public void run() { - mTaskStackView.getStack().removeTask(event.task); - } - }) - .withLayer() - .start(); + TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; + TaskStackLayoutAlgorithm stackLayout = mTaskStackView.getStackAlgorithm(); + TaskStackViewScroller stackScroller = mTaskStackView.getScroller(); + TaskViewTransform tmpTransform = new TaskViewTransform(); + + // Remove the task view after it is docked + stackLayout.getStackTransform(event.task, stackScroller.getStackScroll(), tmpTransform, + null); + tmpTransform.scale = event.taskView.getScaleX(); + tmpTransform.rect.offset(event.taskView.getTranslationX(), + event.taskView.getTranslationY()); + mTaskStackView.updateTaskViewToTransform(event.taskView, tmpTransform, + new TaskViewAnimation(150, mFastOutLinearInInterpolator, + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mTaskStackView.getStack().removeTask(event.task); + } + })); // Dock the task and launch it SystemServicesProxy ssp = Recents.getSystemServices(); @@ -590,21 +594,23 @@ public class RecentsView extends FrameLayout { private void showHistoryButton(final int duration, final ReferenceCountedTrigger postHideHistoryAnimationTrigger) { - mHistoryButton.setVisibility(View.VISIBLE); - mHistoryButton.setAlpha(0f); mHistoryButton.setText(getContext().getString(R.string.recents_history_label_format, mStack.getHistoricalTasks().size())); - postHideHistoryAnimationTrigger.addLastDecrementRunnable(new Runnable() { - @Override - public void run() { - mHistoryButton.animate() - .alpha(1f) - .setDuration(duration) - .setInterpolator(mFastOutSlowInInterpolator) - .withLayer() - .start(); - } - }); + if (mHistoryButton.getVisibility() == View.INVISIBLE) { + mHistoryButton.setVisibility(View.VISIBLE); + mHistoryButton.setAlpha(0f); + postHideHistoryAnimationTrigger.addLastDecrementRunnable(new Runnable() { + @Override + public void run() { + mHistoryButton.animate() + .alpha(1f) + .setDuration(duration) + .setInterpolator(mFastOutSlowInInterpolator) + .withLayer() + .start(); + } + }); + } } /** @@ -618,20 +624,22 @@ public class RecentsView extends FrameLayout { private void hideHistoryButton(int duration, final ReferenceCountedTrigger postHideStackAnimationTrigger) { - mHistoryButton.animate() - .alpha(0f) - .setDuration(duration) - .setInterpolator(mFastOutLinearInInterpolator) - .withEndAction(new Runnable() { - @Override - public void run() { - mHistoryButton.setVisibility(View.INVISIBLE); - postHideStackAnimationTrigger.decrement(); - } - }) - .withLayer() - .start(); - postHideStackAnimationTrigger.increment(); + if (mHistoryButton.getVisibility() == View.VISIBLE) { + mHistoryButton.animate() + .alpha(0f) + .setDuration(duration) + .setInterpolator(mFastOutLinearInInterpolator) + .withEndAction(new Runnable() { + @Override + public void run() { + mHistoryButton.setVisibility(View.INVISIBLE); + postHideStackAnimationTrigger.decrement(); + } + }) + .withLayer() + .start(); + postHideStackAnimationTrigger.increment(); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java index 318801dbcb9f..5fbf421e9836 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java @@ -26,7 +26,6 @@ import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEve import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent; -import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; @@ -58,9 +57,6 @@ class DockRegion { */ public class RecentsViewTouchHandler { - private static final String TAG = "RecentsViewTouchHandler"; - private static final boolean DEBUG = false; - private RecentsView mRv; private Task mDragTask; @@ -128,7 +124,6 @@ public class RecentsViewTouchHandler { mTaskView.setTranslationX(x); mTaskView.setTranslationY(y); - RecentsConfiguration config = Recents.getConfiguration(); if (!ssp.hasDockedTask()) { // Add the dock state drop targets (these take priority) TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java new file mode 100644 index 000000000000..787e330b85d7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents.views; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.RectF; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; + +import com.android.systemui.R; +import com.android.systemui.recents.Recents; +import com.android.systemui.recents.RecentsActivityLaunchState; +import com.android.systemui.recents.RecentsConfiguration; +import com.android.systemui.recents.misc.ReferenceCountedTrigger; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.statusbar.phone.PhoneStatusBar; + +import java.util.List; + +/** + * A helper class to create task view animations for {@link TaskView}s in a {@link TaskStackView}, + * but not the contents of the {@link TaskView}s. + */ +public class TaskStackAnimationHelper { + + /** + * Callbacks from the helper to coordinate view-content animations with view animations. + */ + public interface Callbacks { + /** + * Callback to prepare for the start animation for the launch target {@link TaskView}. + */ + void onPrepareLaunchTargetForEnterAnimation(); + + /** + * Callback to start the animation for the launch target {@link TaskView}. + */ + void onStartLaunchTargetEnterAnimation(int duration, boolean screenPinningEnabled, + ReferenceCountedTrigger postAnimationTrigger); + + /** + * Callback to start the animation for the launch target {@link TaskView} when it is + * launched from Recents. + */ + void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested, + ReferenceCountedTrigger postAnimationTrigger); + } + + private TaskStackView mStackView; + + private Interpolator mFastOutSlowInInterpolator; + private Interpolator mFastOutLinearInInterpolator; + private Interpolator mQuintOutInterpolator; + + private TaskViewTransform mTmpTransform = new TaskViewTransform(); + + public TaskStackAnimationHelper(Context context, TaskStackView stackView) { + mStackView = stackView; + mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_slow_in); + mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_linear_in); + mQuintOutInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.decelerate_quint); + } + + /** + * Prepares the stack views and puts them in their initial animation state while visible, before + * the in-app enter animations start (after the window-transition completes). + */ + public void prepareForEnterAnimation() { + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); + Resources res = mStackView.getResources(); + + TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); + TaskStackViewScroller stackScroller = mStackView.getScroller(); + TaskStack stack = mStackView.getStack(); + Task launchTargetTask = stack.getLaunchTarget(); + + // Break early if there are no tasks + if (stack.getStackTaskCount() == 0) { + return; + } + + int offscreenY = stackLayout.mStackRect.bottom; + int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( + R.dimen.recents_task_view_affiliate_group_enter_offset); + + // Prepare each of the task views for their enter animation from front to back + List taskViews = mStackView.getTaskViews(); + for (int i = taskViews.size() - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); + Task task = tv.getTask(); + boolean currentTaskOccludesLaunchTarget = (launchTargetTask != null && + launchTargetTask.group.isTaskAboveTask(task, launchTargetTask)); + boolean hideTask = (launchTargetTask != null && + launchTargetTask.isFreeformTask() && task.isFreeformTask()); + + // Get the current transform for the task, which will be used to position it offscreen + stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, + null); + + if (hideTask) { + tv.setVisibility(View.INVISIBLE); + } else if (launchState.launchedHasConfigurationChanged) { + // Just load the views as-is + } else if (launchState.launchedFromAppWithThumbnail) { + if (task.isLaunchTarget) { + tv.onPrepareLaunchTargetForEnterAnimation(); + } else if (currentTaskOccludesLaunchTarget) { + // Move the task view slightly lower so we can animate it in + RectF bounds = new RectF(mTmpTransform.rect); + bounds.offset(0, taskViewAffiliateGroupEnterOffset); + tv.setAlpha(0f); + tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, + (int) bounds.right, (int) bounds.bottom); + } + } else if (launchState.launchedFromHome) { + // Move the task view off screen (below) so we can animate it in + RectF bounds = new RectF(mTmpTransform.rect); + bounds.offset(0, offscreenY); + tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, (int) bounds.right, + (int) bounds.bottom); + } + } + } + + /** + * Starts the in-app enter animation, which animates the {@link TaskView}s to their final places + * depending on how Recents was triggered. + */ + public void startEnterAnimation(final ReferenceCountedTrigger postAnimationTrigger) { + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); + Resources res = mStackView.getResources(); + + TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); + TaskStackViewScroller stackScroller = mStackView.getScroller(); + TaskStack stack = mStackView.getStack(); + Task launchTargetTask = stack.getLaunchTarget(); + + // Break early if there are no tasks + if (stack.getStackTaskCount() == 0) { + return; + } + + int taskViewEnterFromAppDuration = res.getInteger( + R.integer.recents_task_enter_from_app_duration); + int taskViewEnterFromHomeDuration = res.getInteger( + R.integer.recents_task_enter_from_home_duration); + int taskViewEnterFromHomeStaggerDelay = res.getInteger( + R.integer.recents_task_enter_from_home_stagger_delay); + + // Create enter animations for each of the views from front to back + List taskViews = mStackView.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); + Task task = tv.getTask(); + boolean currentTaskOccludesLaunchTarget = false; + if (launchTargetTask != null) { + currentTaskOccludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(task, + launchTargetTask); + } + + // Get the current transform for the task, which will be updated to the final transform + // to animate to depending on how recents was invoked + stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, + null); + + if (launchState.launchedFromAppWithThumbnail) { + if (task.isLaunchTarget) { + tv.onStartLaunchTargetEnterAnimation(taskViewEnterFromAppDuration, + mStackView.mScreenPinningEnabled, postAnimationTrigger); + } else { + // Animate the task up if it was occluding the launch target + if (currentTaskOccludesLaunchTarget) { + TaskViewAnimation taskAnimation = new TaskViewAnimation( + taskViewEnterFromAppDuration, PhoneStatusBar.ALPHA_IN, + postAnimationTrigger.decrementOnAnimationEnd()); + postAnimationTrigger.increment(); + mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); + } + } + + } else if (launchState.launchedFromHome) { + // Animate the tasks up + int frontIndex = (taskViewCount - i - 1); + int delay = frontIndex * taskViewEnterFromHomeStaggerDelay; + int duration = taskViewEnterFromHomeDuration + + frontIndex * taskViewEnterFromHomeStaggerDelay; + + TaskViewAnimation taskAnimation = new TaskViewAnimation(delay, + duration, mQuintOutInterpolator, + postAnimationTrigger.decrementOnAnimationEnd()); + postAnimationTrigger.increment(); + mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); + } + } + } + + /** + * Starts an in-app animation to hide all the task views so that we can transition back home. + */ + public void startExitToHomeAnimation(boolean animated, + ReferenceCountedTrigger postAnimationTrigger) { + Resources res = mStackView.getResources(); + TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); + TaskStackViewScroller stackScroller = mStackView.getScroller(); + TaskStack stack = mStackView.getStack(); + + // Break early if there are no tasks + if (stack.getStackTaskCount() == 0) { + return; + } + + int offscreenY = stackLayout.mStackRect.bottom; + int taskViewExitToHomeDuration = res.getInteger( + R.integer.recents_task_exit_to_home_duration); + + // Create the animations for each of the tasks + List taskViews = mStackView.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); + Task task = tv.getTask(); + TaskViewAnimation taskAnimation = new TaskViewAnimation( + animated ? taskViewExitToHomeDuration : 0, mFastOutLinearInInterpolator, + postAnimationTrigger.decrementOnAnimationEnd()); + postAnimationTrigger.increment(); + + stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, + null); + mTmpTransform.rect.offset(0, offscreenY); + mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); + } + } + + /** + * Starts the animation for the launching task view, hiding any tasks that might occlude the + * window transition for the launching task. + */ + public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested, + final ReferenceCountedTrigger postAnimationTrigger) { + Resources res = mStackView.getResources(); + TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); + TaskStackViewScroller stackScroller = mStackView.getScroller(); + + int taskViewExitToAppDuration = res.getInteger( + R.integer.recents_task_exit_to_app_duration); + int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( + R.dimen.recents_task_view_affiliate_group_enter_offset); + + Task launchingTask = launchingTaskView.getTask(); + List taskViews = mStackView.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); + Task task = tv.getTask(); + boolean currentTaskOccludesLaunchTarget = (launchingTask != null && + launchingTask.group.isTaskAboveTask(task, launchingTask)); + + if (tv == launchingTaskView) { + tv.setClipViewInStack(false); + tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration, + screenPinningRequested, postAnimationTrigger); + } else if (currentTaskOccludesLaunchTarget) { + // Animate this task out of view + TaskViewAnimation taskAnimation = new TaskViewAnimation( + taskViewExitToAppDuration, mFastOutLinearInInterpolator, + postAnimationTrigger.decrementOnAnimationEnd()); + postAnimationTrigger.increment(); + + stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, + null); + mTmpTransform.alpha = 0f; + mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); + mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); + } + } + } + + /** + * Starts the delete animation for the specified {@link TaskView}. + */ + public void startDeleteTaskAnimation(Task deleteTask, final TaskView deleteTaskView, + final ReferenceCountedTrigger postAnimationTrigger) { + Resources res = mStackView.getResources(); + TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); + TaskStackViewScroller stackScroller = mStackView.getScroller(); + + int taskViewRemoveAnimDuration = res.getInteger( + R.integer.recents_animate_task_view_remove_duration); + int taskViewRemoveAnimTranslationXPx = res.getDimensionPixelSize( + R.dimen.recents_task_view_remove_anim_translation_x); + + // Disabling clipping with the stack while the view is animating away + deleteTaskView.setClipViewInStack(false); + + // Compose the new animation and transform and star the animation + TaskViewAnimation taskAnimation = new TaskViewAnimation(taskViewRemoveAnimDuration, + PhoneStatusBar.ALPHA_OUT, new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + postAnimationTrigger.decrement(); + + // Re-enable clipping with the stack (we will reuse this view) + deleteTaskView.setClipViewInStack(true); + } + }); + postAnimationTrigger.increment(); + + stackLayout.getStackTransform(deleteTask, stackScroller.getStackScroll(), mTmpTransform, + null); + mTmpTransform.alpha = 0f; + mTmpTransform.rect.offset(taskViewRemoveAnimTranslationXPx, 0); + mStackView.updateTaskViewToTransform(deleteTaskView, mTmpTransform, taskAnimation); + } + + /** + * Starts the animation to hide the {@link TaskView}s when the history is shown. The history + * view's animation will be deferred until all the {@link TaskView}s are finished animating. + */ + public void startShowHistoryAnimation(ReferenceCountedTrigger postAnimationTrigger) { + Resources res = mStackView.getResources(); + TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); + TaskStackViewScroller stackScroller = mStackView.getScroller(); + + int historyTransitionDuration = res.getInteger( + R.integer.recents_history_transition_duration); + + List taskViews = mStackView.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); + Task task = tv.getTask(); + TaskViewAnimation taskAnimation = new TaskViewAnimation( + historyTransitionDuration, PhoneStatusBar.ALPHA_OUT, + postAnimationTrigger.decrementOnAnimationEnd()); + postAnimationTrigger.increment(); + + stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, + null); + mTmpTransform.alpha = 0f; + mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); + } + } + + /** + * Starts the animation to show the {@link TaskView}s when the history is hidden. The + * {@link TaskView} animations will be deferred until the history view has been animated away. + */ + public void startHideHistoryAnimation(final ReferenceCountedTrigger postAnimationTrigger) { + final Resources res = mStackView.getResources(); + final TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); + final TaskStackViewScroller stackScroller = mStackView.getScroller(); + + final int historyTransitionDuration = res.getInteger( + R.integer.recents_history_transition_duration); + + List taskViews = mStackView.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = taskViewCount - 1; i >= 0; i--) { + final TaskView tv = taskViews.get(i); + postAnimationTrigger.addLastDecrementRunnable(new Runnable() { + @Override + public void run() { + TaskViewAnimation taskAnimation = new TaskViewAnimation( + historyTransitionDuration, PhoneStatusBar.ALPHA_IN); + stackLayout.getStackTransform(tv.getTask(), stackScroller.getStackScroll(), + mTmpTransform, null); + mTmpTransform.alpha = 1f; + mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); + } + }); + } + } +} 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 901799e27bf6..1ee22e97166d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -22,7 +22,6 @@ import android.content.res.Resources; import android.graphics.Path; import android.graphics.Rect; import android.util.FloatProperty; -import android.util.Log; import android.util.Property; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; @@ -209,7 +208,6 @@ public class TaskStackLayoutAlgorithm { } Context mContext; - private TaskStackView mStackView; private Interpolator mLinearOutSlowInInterpolator; private StackState mState = StackState.SPLIT; @@ -277,9 +275,12 @@ public class TaskStackLayoutAlgorithm { // The freeform workspace layout FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm; - public TaskStackLayoutAlgorithm(Context context, TaskStackView stackView) { + // The transform to place TaskViews at the front and back of the stack respectively + TaskViewTransform mBackOfStackTransform = new TaskViewTransform(); + TaskViewTransform mFrontOfStackTransform = new TaskViewTransform(); + + public TaskStackLayoutAlgorithm(Context context) { Resources res = context.getResources(); - mStackView = stackView; mFocusedRange = new Range(res.getFloat(R.integer.recents_layout_focused_range_min), res.getFloat(R.integer.recents_layout_focused_range_max)); @@ -315,7 +316,7 @@ public class TaskStackLayoutAlgorithm { */ public void setFocusState(float focusState) { mFocusState = focusState; - mStackView.requestSynchronizeStackViewsWithModel(); + updateFrontBackTransforms(); } /** @@ -365,6 +366,7 @@ public class TaskStackLayoutAlgorithm { mUnfocusedCurveInterpolator = new FreePathInterpolator(mUnfocusedCurve); mFocusedCurve = constructFocusedCurve(); mFocusedCurveInterpolator = new FreePathInterpolator(mFocusedCurve); + updateFrontBackTransforms(); } /** @@ -453,7 +455,7 @@ public class TaskStackLayoutAlgorithm { Utilities.cancelAnimationWithoutCallbacks(mFocusStateAnimator); if (mFocusState > STATE_UNFOCUSED) { float delta = (float) yMovement / (UNFOCUS_MULTIPLIER * mStackRect.height()); - mFocusState -= Math.min(mFocusState, Math.abs(delta)); + setFocusState(mFocusState - Math.min(mFocusState, Math.abs(delta))); } } @@ -479,27 +481,23 @@ public class TaskStackLayoutAlgorithm { RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); RecentsDebugFlags debugFlags = Recents.getDebugFlags(); if (launchState.launchedWithAltTab || debugFlags.isInitialStatePaging()) { - return 1f; + return STATE_FOCUSED; } - return 0f; + return STATE_UNFOCUSED; } /** - * Returns the task progress that would put the task just off the back of the stack. + * Returns the TaskViewTransform that would put the task just off the back of the stack. */ - public float getStackBackTaskProgress(float stackScroll) { - float min = mUnfocusedRange.relativeMin + - mFocusState * (mFocusedRange.relativeMin - mUnfocusedRange.relativeMin); - return stackScroll + min; + public TaskViewTransform getBackOfStackTransform() { + return mBackOfStackTransform; } /** - * Returns the task progress that would put the task just off the front of the stack. + * Returns the TaskViewTransform that would put the task just off the front of the stack. */ - public float getStackFrontTaskProgress(float stackScroll) { - float max = mUnfocusedRange.relativeMax + - mFocusState * (mFocusedRange.relativeMax - mUnfocusedRange.relativeMax); - return stackScroll + max; + public TaskViewTransform getFrontOfStackTransform() { + return mFrontOfStackTransform; } /** @@ -748,4 +746,18 @@ public class TaskStackLayoutAlgorithm { p.cubicTo(0.5f, 1f - peekHeightPct, cpoint2X, cpoint2Y, 1f, 0f); return p; } + + /** + * Updates the current transforms that would put a TaskView at the front and back of the stack. + */ + private void updateFrontBackTransforms() { + float min = mUnfocusedRange.relativeMin + + mFocusState * (mFocusedRange.relativeMin - mUnfocusedRange.relativeMin); + float max = mUnfocusedRange.relativeMax + + mFocusState * (mFocusedRange.relativeMax - mUnfocusedRange.relativeMax); + getStackTransform(min, 0f, mBackOfStackTransform, null); + getStackTransform(max, 0f, mFrontOfStackTransform, null); + mBackOfStackTransform.visible = true; + mFrontOfStackTransform.visible = true; + } } 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 ba67ad8bec4f..33ceaa1ca6dc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.Bundle; @@ -79,7 +78,6 @@ import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -108,6 +106,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f; private static final int DEFAULT_SYNC_STACK_DURATION = 200; + private static final int DRAG_SCALE_DURATION = 175; + private static final float DRAG_SCALE_FACTOR = 1.05f; public static final Property DRAWABLE_ALPHA = new IntProperty("drawableAlpha") { @@ -126,35 +126,34 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal TaskStackLayoutAlgorithm mLayoutAlgorithm; TaskStackViewScroller mStackScroller; TaskStackViewTouchHandler mTouchHandler; + TaskStackAnimationHelper mAnimationHelper; GradientDrawable mFreeformWorkspaceBackground; ObjectAnimator mFreeformWorkspaceBackgroundAnimator; ViewPool mViewPool; + + ArrayList mTaskViews = new ArrayList<>(); ArrayList mCurrentTaskTransforms = new ArrayList<>(); + TaskViewAnimation mDeferredTaskViewUpdateAnimation = null; + DozeTrigger mUIDozeTrigger; Task mFocusedTask; - // Optimizations - int mStackViewsAnimationDuration; + int mTaskCornerRadiusPx; - boolean mStackViewsDirty = true; - boolean mStackViewsClipDirty = true; + + boolean mTaskViewsClipDirty = true; boolean mAwaitingFirstLayout = true; boolean mEnterAnimationComplete = false; + boolean mTouchExplorationEnabled; + boolean mScreenPinningEnabled; Rect mTaskStackBounds = new Rect(); int[] mTmpVisibleRange = new int[2]; Rect mTmpRect = new Rect(); - RectF mTmpTaskRect = new RectF(); - TaskViewTransform mTmpStackBackTransform = new TaskViewTransform(); - TaskViewTransform mTmpStackFrontTransform = new TaskViewTransform(); HashMap mTmpTaskViewMap = new HashMap<>(); - ArrayList mTaskViews = new ArrayList<>(); - List mImmutableTaskViews = new ArrayList<>(); List mTmpTaskViews = new ArrayList<>(); + TaskViewTransform mTmpTransform = new TaskViewTransform(); LayoutInflater mInflater; - boolean mTouchExplorationEnabled; - boolean mScreenPinningEnabled; - Interpolator mFastOutSlowInInterpolator; // A convenience update listener to request updating clipping of tasks @@ -162,7 +161,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { - requestUpdateStackViewsClip(); + mTaskViewsClipDirty = true; + invalidate(); } }; @@ -190,10 +190,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal setStack(stack); mViewPool = new ViewPool<>(context, this); mInflater = LayoutInflater.from(context); - mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this); + mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context); mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm); mStackScroller.setCallbacks(this); mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller); + mAnimationHelper = new TaskStackAnimationHelper(context, this); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_slow_in); mTaskCornerRadiusPx = res.getDimensionPixelSize( @@ -266,12 +267,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mTaskViews.add((TaskView) v); } } - mImmutableTaskViews = Collections.unmodifiableList(mTaskViews); } /** Gets the list of task views */ List getTaskViews() { - return mImmutableTaskViews; + return mTaskViews; } /** @@ -330,44 +330,16 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Reset the stack state mStack.reset(); - mStackViewsDirty = true; - mStackViewsClipDirty = true; + mTaskViewsClipDirty = true; mAwaitingFirstLayout = true; mEnterAnimationComplete = false; - if (mUIDozeTrigger != null) { - mUIDozeTrigger.stopDozing(); - mUIDozeTrigger.resetTrigger(); - } + mUIDozeTrigger.stopDozing(); + mUIDozeTrigger.resetTrigger(); mStackScroller.reset(); mLayoutAlgorithm.reset(); requestLayout(); } - /** Requests that the views be synchronized with the model */ - void requestSynchronizeStackViewsWithModel() { - requestSynchronizeStackViewsWithModel(0); - } - void requestSynchronizeStackViewsWithModel(int duration) { - if (!mStackViewsDirty) { - invalidate(); - mStackViewsDirty = true; - } - if (mAwaitingFirstLayout) { - // Skip the animation if we are awaiting first layout - mStackViewsAnimationDuration = 0; - } else { - mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration); - } - } - - /** Requests that the views clipping be updated. */ - void requestUpdateStackViewsClip() { - if (!mStackViewsClipDirty) { - invalidate(); - mStackViewsClipDirty = true; - } - } - /** Returns the stack algorithm for this task stack. */ public TaskStackLayoutAlgorithm getStackAlgorithm() { return mLayoutAlgorithm; @@ -401,15 +373,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal TaskViewTransform frontTransform = null; for (int i = taskCount - 1; i >= 0; i--) { Task task = tasks.get(i); + TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll, + taskTransforms.get(i), frontTransform); + + // For freeform tasks, only calculate the stack transform and skip the calculation of + // the visible stack indices if (task.isFreeformTask()) { continue; } - TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll, - taskTransforms.get(i), frontTransform); - if (DEBUG) { - Log.d(TAG, "updateStackTransform: " + i + ", " + transform.visible); - } if (transform.visible) { if (frontMostVisibleIndex < 0) { frontMostVisibleIndex = i; @@ -435,144 +407,155 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1; } - /** Synchronizes the views with the model */ - boolean synchronizeStackViewsWithModel() { - if (mStackViewsDirty) { - // Get all the task transforms - ArrayList tasks = mStack.getStackTasks(); - float stackScroll = mStackScroller.getStackScroll(); - int[] visibleStackRange = mTmpVisibleRange; - boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms, tasks, - stackScroll, visibleStackRange); - boolean hasStackBackTransform = false; - boolean hasStackFrontTransform = false; - if (DEBUG) { - Log.d(TAG, "visibleRange: " + visibleStackRange[0] + " to " + visibleStackRange[1]); - } + /** + * Updates the children {@link TaskView}s to match the tasks in the current {@link TaskStack}. + * This call does not update the {@link TaskView}s to their position in the layout except when + * they are initially picked up from the pool, when they will be placed in a suitable initial + * position. + */ + private void bindTaskViewsWithStack() { + final float stackScroll = mStackScroller.getStackScroll(); + final int[] visibleStackRange = mTmpVisibleRange; + + // Get all the task transforms + final ArrayList tasks = mStack.getStackTasks(); + final boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms, tasks, + stackScroll, visibleStackRange); + + // Return all the invisible children to the pool + mTmpTaskViewMap.clear(); + final List taskViews = getTaskViews(); + final int taskViewCount = taskViews.size(); + int lastFocusedTaskIndex = -1; + for (int i = taskViewCount - 1; i >= 0; i--) { + final TaskView tv = taskViews.get(i); + final Task task = tv.getTask(); + final int taskIndex = mStack.indexOfStackTask(task); - // Return all the invisible children to the pool - mTmpTaskViewMap.clear(); - List taskViews = getTaskViews(); - int lastFocusedTaskIndex = -1; - int taskViewCount = taskViews.size(); - for (int i = taskViewCount - 1; i >= 0; i--) { - TaskView tv = taskViews.get(i); - Task task = tv.getTask(); - int taskIndex = mStack.indexOfStackTask(task); - if (task.isFreeformTask() || - visibleStackRange[1] <= taskIndex && taskIndex <= visibleStackRange[0]) { - mTmpTaskViewMap.put(task, tv); - } else { - if (mTouchExplorationEnabled) { - lastFocusedTaskIndex = taskIndex; - resetFocusedTask(task); - } - if (DEBUG) { - Log.d(TAG, "returning to pool: " + task.key); - } - mViewPool.returnViewToPool(tv); + if (task.isFreeformTask() || + visibleStackRange[1] <= taskIndex && taskIndex <= visibleStackRange[0]) { + mTmpTaskViewMap.put(task, tv); + } else { + if (mTouchExplorationEnabled) { + lastFocusedTaskIndex = taskIndex; + resetFocusedTask(task); } + mViewPool.returnViewToPool(tv); } + } - // Pick up all the freeform tasks - int firstVisStackIndex = isValidVisibleStackRange ? visibleStackRange[0] : 0; - for (int i = mStack.getStackTaskCount() - 1; i >= firstVisStackIndex; i--) { - Task task = tasks.get(i); - if (!task.isFreeformTask()) { - continue; - } - TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll, - mCurrentTaskTransforms.get(i), null); - TaskView tv = mTmpTaskViewMap.get(task); - if (tv == null) { - if (DEBUG) { - Log.d(TAG, "picking up from pool: " + task.key); - } - tv = mViewPool.pickUpViewFromPool(task, task); - } else { - // Reattach it in the right z order - int taskIndex = mStack.indexOfStackTask(task); - int insertIndex = findTaskViewInsertIndex(task, taskIndex); - if (insertIndex != getTaskViews().indexOf(tv)){ - detachViewFromParent(tv); - attachViewToParent(tv, insertIndex, tv.getLayoutParams()); - } - } - - // Animate the task into place - tv.updateViewPropertiesToTaskTransform(transform, 0, - mStackViewsAnimationDuration, mFastOutSlowInInterpolator, - mRequestUpdateClippingListener); + // Pick up all the newly visible children + int lastVisStackIndex = isValidVisibleStackRange ? visibleStackRange[1] : 0; + for (int i = mStack.getStackTaskCount() - 1; i >= lastVisStackIndex; i--) { + final Task task = tasks.get(i); + final TaskViewTransform transform = mCurrentTaskTransforms.get(i); - // Update the task views list after adding the new task view - updateTaskViewsList(); + // Skip the invisible non-freeform stack tasks + if (i > visibleStackRange[0] && !task.isFreeformTask()) { + continue; } - // Pick up all the newly visible children and update all the existing children - for (int i = visibleStackRange[0]; - isValidVisibleStackRange && i >= visibleStackRange[1]; i--) { - Task task = tasks.get(i); - TaskViewTransform transform = mCurrentTaskTransforms.get(i); - TaskView tv = mTmpTaskViewMap.get(task); - - if (tv == null) { - tv = mViewPool.pickUpViewFromPool(task, task); - if (mStackViewsAnimationDuration > 0) { - // For items in the list, put them in start animating them from the - // approriate ends of the list where they are expected to appear - if (Float.compare(transform.p, 0f) <= 0) { - if (!hasStackBackTransform) { - hasStackBackTransform = true; - mLayoutAlgorithm.getStackTransform( - mLayoutAlgorithm.getStackBackTaskProgress(0f), 0f, - mTmpStackBackTransform, null); - } - tv.updateViewPropertiesToTaskTransform(mTmpStackBackTransform, 0, 0, - mFastOutSlowInInterpolator, mRequestUpdateClippingListener); - } else { - if (!hasStackFrontTransform) { - hasStackFrontTransform = true; - mLayoutAlgorithm.getStackTransform( - mLayoutAlgorithm.getStackFrontTaskProgress(0f), 0f, - mTmpStackFrontTransform, null); - } - tv.updateViewPropertiesToTaskTransform(mTmpStackFrontTransform, 0, 0, - mFastOutSlowInInterpolator, mRequestUpdateClippingListener); - } + TaskView tv = mTmpTaskViewMap.get(task); + if (tv == null) { + tv = mViewPool.pickUpViewFromPool(task, task); + if (task.isFreeformTask()) { + tv.updateViewPropertiesToTaskTransform(transform, TaskViewAnimation.IMMEDIATE, + mRequestUpdateClippingListener); + } else { + if (Float.compare(transform.p, 0f) <= 0) { + tv.updateViewPropertiesToTaskTransform( + mLayoutAlgorithm.getBackOfStackTransform(), + TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener); + } else { + tv.updateViewPropertiesToTaskTransform( + mLayoutAlgorithm.getFrontOfStackTransform(), + TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener); } } - - // Animate the task into place, the clip for stack tasks will be calculated in - // clipTaskViews() - tv.updateViewPropertiesToTaskTransform(transform, - tv.getViewBounds().getClipBottom(), mStackViewsAnimationDuration, - mFastOutSlowInInterpolator, mRequestUpdateClippingListener); + } else { + // Reattach it in the right z order + final int taskIndex = mStack.indexOfStackTask(task); + final int insertIndex = findTaskViewInsertIndex(task, taskIndex); + if (insertIndex != getTaskViews().indexOf(tv)){ + detachViewFromParent(tv); + attachViewToParent(tv, insertIndex, tv.getLayoutParams()); + updateTaskViewsList(); + } } + } - // Update the focus if the previous focused task was returned to the view pool - if (lastFocusedTaskIndex != -1) { - if (lastFocusedTaskIndex < visibleStackRange[1]) { - setFocusedTask(visibleStackRange[1], false /* animated */, - true /* requestViewFocus */); - } else { - setFocusedTask(visibleStackRange[0], false /* animated */, - true /* requestViewFocus */); - } + // Update the focus if the previous focused task was returned to the view pool + if (lastFocusedTaskIndex != -1) { + if (lastFocusedTaskIndex < visibleStackRange[1]) { + setFocusedTask(visibleStackRange[1], false /* scrollToTask */, + true /* requestViewFocus */); + } else { + setFocusedTask(visibleStackRange[0], false /* scrollToTask */, + true /* requestViewFocus */); } + } + } - // Reset the request-synchronize params - mStackViewsAnimationDuration = 0; - mStackViewsDirty = false; - mStackViewsClipDirty = true; - return true; + /** + * Cancels any existing {@link TaskView} animations, and updates each {@link TaskView} to its + * current position as defined by the {@link TaskStackLayoutAlgorithm}. + */ + private void updateTaskViewsToLayout(TaskViewAnimation animation) { + // If we had a deferred animation, cancel that + mDeferredTaskViewUpdateAnimation = null; + + // Cancel all task view animations + cancelAllTaskViewAnimations(); + + // Fetch the current set of TaskViews + bindTaskViewsWithStack(); + + // Animate them to their final transforms with the given animation + List taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + final TaskView tv = taskViews.get(i); + final int taskIndex = mStack.indexOfStackTask(tv.getTask()); + final TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex); + + updateTaskViewToTransform(tv, transform, animation); + } + } + + /** + * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame. + */ + private void updateTaskViewsToLayoutOnNextFrame(TaskViewAnimation animation) { + mDeferredTaskViewUpdateAnimation = animation; + postInvalidateOnAnimation(); + } + + /** + * Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a + * given set of {@link TaskViewAnimation} properties. + */ + public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform, + TaskViewAnimation animation) { + taskView.updateViewPropertiesToTaskTransform(transform, animation, + mRequestUpdateClippingListener); + } + + /** + * Cancels all {@link TaskView} animations. + */ + private void cancelAllTaskViewAnimations() { + List taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + final TaskView tv = taskViews.get(i); + tv.cancelTransformAnimation(); } - return false; } /** * Updates the clip for each of the task views from back to front. */ - void clipTaskViews(boolean forceUpdate) { + private void clipTaskViews() { RecentsConfiguration config = Recents.getConfiguration(); // Update the clip on each task child @@ -606,12 +589,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } } - tv.getViewBounds().setClipBottom(clipBottom, forceUpdate); + tv.getViewBounds().setClipBottom(clipBottom); if (!config.useHardwareLayers) { tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom()); } } - mStackViewsClipDirty = false; + mTaskViewsClipDirty = false; } /** Updates the min and max virtual scroll bounds */ @@ -642,16 +625,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal * * @return whether or not the stack will scroll as a part of this focus change */ - private boolean setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) { - return setFocusedTask(taskIndex, scrollToTask, animated, true); - } - - /** - * Sets the focused task to the provided (bounded taskIndex). - * - * @return whether or not the stack will scroll as a part of this focus change - */ - private boolean setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated, + private boolean setFocusedTask(int taskIndex, boolean scrollToTask, final boolean requestViewFocus) { // Find the next task to focus int newFocusedTaskIndex = mStack.getStackTaskCount() > 0 ? @@ -672,7 +646,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void run() { TaskView tv = getChildViewForTask(newFocusedTask); if (tv != null) { - tv.setFocusedState(true, animated, requestViewFocus); + tv.setFocusedState(true, requestViewFocus); } } }; @@ -687,10 +661,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Cancel any running enter animations at this point when we scroll as well if (!mEnterAnimationComplete) { - final List taskViews = getTaskViews(); - for (TaskView tv : taskViews) { - tv.cancelEnterRecentsAnimation(); - } + cancelAllTaskViewAnimations(); } } else { focusTaskRunnable.run(); @@ -765,7 +736,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } if (newIndex != -1) { - boolean willScroll = setFocusedTask(newIndex, true, animated); + boolean willScroll = setFocusedTask(newIndex, true /* scrollToTask */, + true /* requestViewFocus */); if (willScroll && cancelWindowAnimations) { // As we iterate to the next/previous task, cancel any current/lagging window // transition animations @@ -781,7 +753,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (task != null) { TaskView tv = getChildViewForTask(task); if (tv != null) { - tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */); + tv.setFocusedState(false, false /* requestViewFocus */); } } mFocusedTask = null; @@ -886,12 +858,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void computeScroll() { - mStackScroller.computeScroll(); - // Synchronize the views - synchronizeStackViewsWithModel(); - clipTaskViews(false /* forceUpdate */); - // Notify accessibility - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); + if (mStackScroller.computeScroll()) { + // Notify accessibility + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); + } + if (mDeferredTaskViewUpdateAnimation != null) { + updateTaskViewsToLayout(mDeferredTaskViewUpdateAnimation); + mTaskViewsClipDirty = true; + mDeferredTaskViewUpdateAnimation = null; + } + if (mTaskViewsClipDirty) { + clipTaskViews(); + } } /** Computes the stack and task rects */ @@ -938,13 +916,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Compute our stack/task rects computeRects(mTaskStackBounds); - // If this is the first layout, then scroll to the front of the stack and synchronize the - // stack views immediately to load all the views + // If this is the first layout, then scroll to the front of the stack, then update the + // TaskViews with the stack so that we can lay them out if (mAwaitingFirstLayout) { mStackScroller.setStackScrollToInitialState(); - requestSynchronizeStackViewsWithModel(); - synchronizeStackViewsWithModel(); } + bindTaskViewsWithStack(); // Measure each of the TaskViews mTmpTaskViews.clear(); @@ -994,44 +971,25 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom); } - if (mAwaitingFirstLayout) { - mAwaitingFirstLayout = false; - onFirstLayout(); - } - - requestSynchronizeStackViewsWithModel(); if (changed) { if (mStackScroller.isScrollOutOfBounds()) { mStackScroller.boundScroll(); } - synchronizeStackViewsWithModel(); - requestUpdateStackViewsClip(); - clipTaskViews(true /* forceUpdate */); + } + updateTaskViewsToLayout(TaskViewAnimation.IMMEDIATE); + clipTaskViews(); + + if (mAwaitingFirstLayout || !mEnterAnimationComplete) { + mAwaitingFirstLayout = false; + onFirstLayout(); + return; } } /** Handler for the first layout. */ void onFirstLayout() { - int offscreenY = mLayoutAlgorithm.mStackRect.bottom; - - // Find the launch target task - Task launchTargetTask = mStack.getLaunchTarget(); - List taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - - // Prepare the first view for its enter animation - for (int i = taskViewCount - 1; i >= 0; i--) { - TaskView tv = taskViews.get(i); - Task task = tv.getTask(); - boolean hideTask = false; - boolean occludesLaunchTarget = false; - if (launchTargetTask != null) { - occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(task, - launchTargetTask); - hideTask = launchTargetTask.isFreeformTask() && task.isFreeformTask(); - } - tv.prepareEnterRecentsAnimation(hideTask, occludesLaunchTarget, offscreenY); - } + // Setup the view for the enter animation + mAnimationHelper.prepareForEnterAnimation(); // Animate in the freeform workspace animateFreeformWorkspaceBackgroundAlpha( @@ -1044,7 +1002,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal RecentsActivityLaunchState launchState = config.getLaunchState(); int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getStackTaskCount()); if (focusedTaskIndex != -1) { - setFocusedTask(focusedTaskIndex, false /* scrollToTask */, false /* animated */, + setFocusedTask(focusedTaskIndex, false /* scrollToTask */, false /* requestViewFocus */); } @@ -1057,92 +1015,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } - /** Requests this task stacks to start it's enter-recents animation */ - public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { - if (mStack.getStackTaskCount() > 0) { - // Find the launch target task - Task launchTargetTask = mStack.getLaunchTarget(); - List taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - - // Animate all the task views into view - for (int i = taskViewCount - 1; i >= 0; i--) { - TaskView tv = taskViews.get(i); - Task task = tv.getTask(); - ctx.currentTaskTransform = new TaskViewTransform(); - ctx.currentStackViewIndex = i; - ctx.currentStackViewCount = taskViewCount; - ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect; - ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) && - launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); - ctx.isScreenPinningEnabled = mScreenPinningEnabled; - ctx.updateListener = mRequestUpdateClippingListener; - mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), - ctx.currentTaskTransform, null); - tv.startEnterRecentsAnimation(ctx); - } - - // Add a runnable to the post animation ref counter to clear all the views - ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { - @Override - public void run() { - // Poke the dozer to restart the trigger after the animation completes - mUIDozeTrigger.poke(); - - // Update the focused state here -- since we only set the focused task without - // requesting view focus in onFirstLayout(), actually request view focus and - // animate the focused state if we are alt-tabbing now, after the window enter - // animation is completed - if (mFocusedTask != null) { - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); - setFocusedTask(mStack.indexOfStackTask(mFocusedTask), - false /* scrollToTask */, launchState.launchedWithAltTab); - } - } - }); - } - } - - /** Requests this task stack to start it's exit-recents animation. */ - public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { - // Stop any scrolling - mStackScroller.stopScroller(); - mStackScroller.stopBoundScrollAnimation(); - // Animate all the task views out of view - ctx.offscreenTranslationY = mLayoutAlgorithm.mStackRect.bottom; - // Dismiss the freeform workspace background - int taskViewExitToHomeDuration = getResources().getInteger( - R.integer.recents_task_exit_to_home_duration); - animateFreeformWorkspaceBackgroundAlpha(0, taskViewExitToHomeDuration, - mFastOutSlowInInterpolator); - - List taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - for (int i = 0; i < taskViewCount; i++) { - TaskView tv = taskViews.get(i); - tv.startExitToHomeAnimation(ctx); - } - } - - /** Animates a task view in this stack as it launches. */ - public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) { - Task launchTargetTask = tv.getTask(); - List taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - for (int i = 0; i < taskViewCount; i++) { - TaskView t = taskViews.get(i); - if (t == tv) { - t.setClipViewInStack(false); - t.startLaunchTaskAnimation(r, true, true, lockToTask); - } else { - boolean occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(t.getTask(), - launchTargetTask); - t.startLaunchTaskAnimation(null, false, occludesLaunchTarget, lockToTask); - } - } - } - public boolean isTransformedTouchPointInView(float x, float y, TaskView tv) { final float[] point = new float[2]; point[0] = x; @@ -1178,11 +1050,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal * Launches the freeform tasks. */ public boolean launchFreeformTasks() { - Task frontTask = mStack.getStackFrontMostTask(); - if (frontTask != null && frontTask.isFreeformTask()) { - EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask), - frontTask, null, INVALID_STACK_ID, false)); - return true; + ArrayList tasks = mStack.getFreeformTasks(); + if (!tasks.isEmpty()) { + Task frontTask = tasks.get(tasks.size() - 1); + if (frontTask != null && frontTask.isFreeformTask()) { + EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask), + frontTask, null, INVALID_STACK_ID, false)); + return true; + } } return false; } @@ -1195,7 +1070,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal updateLayout(true); // Animate all the tasks into place - requestSynchronizeStackViewsWithModel(DEFAULT_SYNC_STACK_DURATION); + updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, + mFastOutSlowInInterpolator)); } @Override @@ -1242,9 +1118,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackScroller.setStackScroll(mStackScroller.getStackScroll() + stackScrollOffset); mStackScroller.boundScroll(); } - - // Animate all the tasks into place - requestSynchronizeStackViewsWithModel(DEFAULT_SYNC_STACK_DURATION); } else { // Remove the view associated with this task, we can't rely on updateTransforms // to work here because the task is no longer in the list @@ -1255,11 +1128,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Update the min/max scroll and animate other task views into their new positions updateLayout(true); - - // Animate all the tasks into place - requestSynchronizeStackViewsWithModel(DEFAULT_SYNC_STACK_DURATION); } + // Animate all the tasks into place + updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, + mFastOutSlowInInterpolator)); + // Update the new front most task's action button if (mScreenPinningEnabled && newFrontMostTask != null) { TaskView frontTv = getChildViewForTask(newFrontMostTask); @@ -1303,19 +1177,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Reset the view properties and view state tv.resetViewProperties(); - tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */); + tv.setFocusedState(false, false /* requestViewFocus */); tv.setClipViewInStack(false); if (mScreenPinningEnabled) { - tv.hideActionButton(); + tv.hideActionButton(false /* fadeOut */, 0 /* duration */, false /* scaleDown */, null); } } @Override public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) { - // It is possible for a view to be returned to the view pool before it is laid out, - // which means that we will need to relayout the view when it is first used next. - boolean requiresRelayout = tv.getWidth() <= 0 && !isNewView; - // Rebind the task and request that this task's data be filled into the TaskView tv.onTaskBound(task); @@ -1334,9 +1204,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal addView(tv, insertIndex); } else { attachViewToParent(tv, insertIndex, tv.getLayoutParams()); - if (requiresRelayout) { - tv.requestLayout(); - } } // Update the task views list after adding the new task view updateTaskViewsList(); @@ -1346,7 +1213,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal tv.setTouchEnabled(true); tv.setClipViewInStack(true); if (mFocusedTask == task) { - tv.setFocusedState(true, false /* animated */, false /* requestViewFocus */); + tv.setFocusedState(true, false /* requestViewFocus */); } // Restore the action button visibility if it is the front most task view @@ -1364,16 +1231,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void onTaskViewClipStateChanged(TaskView tv) { - requestUpdateStackViewsClip(); + clipTaskViews(); } /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ @Override - public void onScrollChanged(float prevScroll, float curScroll) { + public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) { mUIDozeTrigger.poke(); - requestSynchronizeStackViewsWithModel(); - postInvalidateOnAnimation(); + updateTaskViewsToLayoutOnNextFrame(animation); if (shouldShowHistoryButton() && prevScroll > SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD && @@ -1400,12 +1266,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal final TaskView tv = getChildViewForTask(t); if (tv != null) { // For visible children, defer removing the task until after the animation - tv.startDeleteTaskAnimation(new Runnable() { - @Override - public void run() { - removeTaskViewFromStack(tv); - } - }, 0); + tv.dismissTask(); } else { // Otherwise, remove the task from the stack immediately mStack.removeTask(t); @@ -1420,9 +1281,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } public final void onBusEvent(LaunchTaskStartedEvent event) { - event.getAnimationTrigger().increment(); - startLaunchTaskAnimation(event.taskView, event.getAnimationTrigger().decrementAsRunnable(), - event.screenPinningRequested); + mAnimationHelper.startLaunchTaskAnimation(event.taskView, event.screenPinningRequested, + event.getAnimationTrigger()); } public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { @@ -1431,9 +1291,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackScroller.stopBoundScrollAnimation(); // Start the task animations - ViewAnimation.TaskViewExitContext context = new ViewAnimation.TaskViewExitContext( - event.getAnimationTrigger()); - startExitToHomeAnimation(context); + mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger()); // Dismiss the freeform workspace background int taskViewExitToHomeDuration = getResources().getInteger( @@ -1454,9 +1312,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public final void onBusEvent(final DismissTaskViewEvent event) { // For visible children, defer removing the task until after the animation - event.getAnimationTrigger().increment(); - event.taskView.startDeleteTaskAnimation( - event.getAnimationTrigger().decrementAsRunnable(), 0); + mAnimationHelper.startDeleteTaskAnimation(event.task, event.taskView, + event.getAnimationTrigger()); } public final void onBusEvent(TaskViewDismissedEvent event) { @@ -1489,6 +1346,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackScroller.animateScroll(mStackScroller.getStackScroll(), mLayoutAlgorithm.mInitialScrollP, null); } + + // Enlarge the dragged view slightly + float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR; + mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), + mTmpTransform, null); + mTmpTransform.scale = finalScale; + updateTaskViewToTransform(event.taskView, mTmpTransform, + new TaskViewAnimation(DRAG_SCALE_DURATION, mFastOutSlowInInterpolator)); } public final void onBusEvent(DragStartInitializeDropTargetsEvent event) { @@ -1534,8 +1399,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } }); } - event.getAnimationTrigger().increment(); - event.taskView.animate().withEndAction(event.getAnimationTrigger().decrementAsRunnable()); // We translated the view but we need to animate it back from the current layout-space rect // to its final layout-space rect @@ -1549,8 +1412,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal event.taskView.setLeftTopRightBottom(taskViewRect.left, taskViewRect.top, taskViewRect.right, taskViewRect.bottom); - // Animate the tack view back into position - requestSynchronizeStackViewsWithModel(250); + // Animate all the TaskViews back into position + mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), + mTmpTransform, null); + event.getAnimationTrigger().increment(); + updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, + mFastOutSlowInInterpolator)); + updateTaskViewToTransform(event.taskView, mTmpTransform, + new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, mFastOutSlowInInterpolator, + event.getAnimationTrigger().decrementOnAnimationEnd())); } public final void onBusEvent(StackViewScrolledEvent event) { @@ -1562,7 +1432,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Cancel the previous task's window transition before animating the focused state EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null)); } - mLayoutAlgorithm.animateFocusState(mLayoutAlgorithm.getDefaultFocusState()); } public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) { @@ -1570,9 +1439,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (mStack.getStackTaskCount() > 0) { // Start the task enter animations - ViewAnimation.TaskViewEnterContext context = new ViewAnimation.TaskViewEnterContext( - event.getAnimationTrigger()); - startEnterRecentsAnimation(context); + mAnimationHelper.startEnterAnimation(event.getAnimationTrigger()); // Add a runnable to the post animation ref counter to clear all the views event.addPostAnimationCallback(new Runnable() { @@ -1609,48 +1476,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } public final void onBusEvent(ShowHistoryEvent event) { - // The history view's animation will be deferred until all the stack task views are animated - // away - int historyTransitionDuration = - getResources().getInteger(R.integer.recents_history_transition_duration); - List taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - for (int i = taskViewCount - 1; i >= 0; i--) { - TaskView tv = taskViews.get(i); - tv.animate() - .alpha(0f) - .setDuration(historyTransitionDuration) - .setUpdateListener(null) - .setListener(null) - .withLayer() - .withEndAction(event.getAnimationTrigger().decrementAsRunnable()) - .start(); - event.getAnimationTrigger().increment(); - } + mAnimationHelper.startShowHistoryAnimation(event.getAnimationTrigger()); } public final void onBusEvent(HideHistoryEvent event) { - // The stack task view animations will be deferred until the history view has been animated - // away - final int historyTransitionDuration = - getResources().getInteger(R.integer.recents_history_transition_duration); - List taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - for (int i = taskViewCount - 1; i >= 0; i--) { - final TaskView tv = taskViews.get(i); - event.addPostAnimationCallback(new Runnable() { - @Override - public void run() { - tv.animate() - .alpha(1f) - .setDuration(historyTransitionDuration) - .setUpdateListener(null) - .setListener(null) - .withLayer() - .start(); - } - }); - } + mAnimationHelper.startHideHistoryAnimation(event.getAnimationTrigger()); } /** 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 56942a87e0f5..32f02acd9d77 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -34,7 +34,7 @@ public class TaskStackViewScroller { private static final boolean DEBUG = false; public interface TaskStackViewScrollerCallbacks { - void onScrollChanged(float prevScroll, float curScroll); + void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation); } Context mContext; @@ -57,7 +57,6 @@ public class TaskStackViewScroller { mLayoutAlgorithm = layoutAlgorithm; mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.linear_out_slow_in); - setStackScroll(getStackScroll()); } /** Resets the task scroller. */ @@ -75,12 +74,22 @@ public class TaskStackViewScroller { return mStackScrollP; } - /** Sets the current stack scroll */ + /** + * Sets the current stack scroll immediately. + */ public void setStackScroll(float s) { + setStackScroll(s, TaskViewAnimation.IMMEDIATE); + } + + /** + * Sets the current stack scroll, but indicates to the callback the preferred animation to + * update to this new scroll. + */ + public void setStackScroll(float s, TaskViewAnimation animation) { float prevStackScroll = mStackScrollP; mStackScrollP = s; if (mCb != null) { - mCb.onScrollChanged(prevStackScroll, mStackScrollP); + mCb.onScrollChanged(prevStackScroll, mStackScrollP, animation); } } 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 d861d34fd2c9..5852d07a9275 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -295,6 +295,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect; if (freeformRect.top <= y && y <= freeformRect.bottom) { if (mSv.launchFreeformTasks()) { + // TODO: Animate Recents away as we launch the freeform tasks return; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 3f7b99de545e..b9fefde89223 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -31,7 +31,9 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Log; +import android.util.FloatProperty; +import android.util.IntProperty; +import android.util.Property; import android.view.MotionEvent; import android.view.View; import android.view.ViewOutlineProvider; @@ -39,10 +41,10 @@ import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; + import com.android.systemui.R; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivity; -import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.LaunchTaskEvent; @@ -50,28 +52,57 @@ import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; +import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import com.android.systemui.statusbar.phone.PhoneStatusBar; +import java.util.ArrayList; + import static android.app.ActivityManager.StackId.INVALID_STACK_ID; /* A task view */ public class TaskView extends FrameLayout implements Task.TaskCallbacks, - View.OnClickListener, View.OnLongClickListener { - - private final static String TAG = "TaskView"; - private final static boolean DEBUG = false; + TaskStackAnimationHelper.Callbacks, View.OnClickListener, View.OnLongClickListener { /** The TaskView callbacks */ interface TaskViewCallbacks { void onTaskViewClipStateChanged(TaskView tv); } + /** + * The dim overlay is generally calculated from the task progress, but occasionally (like when + * launching) needs to be animated independently of the task progress. + */ + public static final Property DIM = + new IntProperty("dim") { + @Override + public void setValue(TaskView tv, int dim) { + tv.setDim(dim); + } + + @Override + public Integer get(TaskView tv) { + return tv.getDim(); + } + }; + + public static final Property TASK_PROGRESS = + new FloatProperty("taskProgress") { + @Override + public void setValue(TaskView tv, float p) { + tv.setTaskProgress(p); + } + + @Override + public Float get(TaskView tv) { + return tv.getTaskProgress(); + } + }; + float mTaskProgress; - ObjectAnimator mTaskProgressAnimator; float mMaxDimScale; int mDimAlpha; AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(3f); @@ -81,9 +112,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, Task mTask; boolean mTaskDataLoaded; - boolean mClipViewInStack; + boolean mClipViewInStack = true; AnimateableViewBounds mViewBounds; - private AnimatorSet mClipAnimation; + + private AnimatorSet mTransformAnimation; + private ArrayList mTmpAnimators = new ArrayList<>(); View mContent; TaskViewThumbnail mThumbnailView; @@ -94,18 +127,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, Point mDownTouchPos = new Point(); Interpolator mFastOutSlowInInterpolator; - Interpolator mFastOutLinearInInterpolator; - Interpolator mQuintOutInterpolator; - - // Optimizations - ValueAnimator.AnimatorUpdateListener mUpdateDimListener = - new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - setTaskProgress((Float) animation.getAnimatedValue()); - } - }; - public TaskView(Context context) { this(context, null); @@ -124,17 +145,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, RecentsConfiguration config = Recents.getConfiguration(); Resources res = context.getResources(); mMaxDimScale = res.getInteger(R.integer.recents_max_task_stack_view_dim) / 255f; - mClipViewInStack = true; mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize( R.dimen.recents_task_view_rounded_corners_radius)); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_slow_in); - mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, - com.android.internal.R.interpolator.fast_out_linear_in); - mQuintOutInterpolator = AnimationUtils.loadInterpolator(context, - com.android.internal.R.interpolator.decelerate_quint); - setTaskProgress(getTaskProgress()); - setDim(getDim()); if (config.fakeShadows) { setBackground(new FakeShadowDrawable(res, config)); } @@ -228,294 +242,69 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, invalidateOutline(); } - /** Synchronizes this view's properties with the task's transform */ - void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int clipBottom, - int duration, Interpolator interpolator, - ValueAnimator.AnimatorUpdateListener updateCallback) { + void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, + TaskViewAnimation toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) { RecentsConfiguration config = Recents.getConfiguration(); - Utilities.cancelAnimationWithoutCallbacks(mClipAnimation); - - // Apply the transform - toTransform.applyToTaskView(this, duration, interpolator, false, - !config.fakeShadows, updateCallback); - - // Update the clipping - if (duration > 0) { - mClipAnimation = new AnimatorSet(); - mClipAnimation.playTogether( - ObjectAnimator.ofInt(mViewBounds, AnimateableViewBounds.CLIP_BOTTOM, - mViewBounds.getClipBottom(), clipBottom), - ObjectAnimator.ofInt(this, TaskViewTransform.LEFT, getLeft(), - (int) toTransform.rect.left), - ObjectAnimator.ofInt(this, TaskViewTransform.TOP, getTop(), - (int) toTransform.rect.top), - ObjectAnimator.ofInt(this, TaskViewTransform.RIGHT, getRight(), - (int) toTransform.rect.right), - ObjectAnimator.ofInt(this, TaskViewTransform.BOTTOM, getBottom(), - (int) toTransform.rect.bottom), - ObjectAnimator.ofFloat(mThumbnailView, TaskViewThumbnail.BITMAP_SCALE, - mThumbnailView.getBitmapScale(), toTransform.thumbnailScale)); - mClipAnimation.setStartDelay(toTransform.startDelay); - mClipAnimation.setDuration(duration); - mClipAnimation.setInterpolator(interpolator); - mClipAnimation.start(); - } else { - mViewBounds.setClipBottom(clipBottom, false /* forceUpdate */); - mThumbnailView.setBitmapScale(toTransform.thumbnailScale); - setLeftTopRightBottom((int) toTransform.rect.left, (int) toTransform.rect.top, - (int) toTransform.rect.right, (int) toTransform.rect.bottom); - } - if (!config.useHardwareLayers) { - mThumbnailView.updateThumbnailVisibility(clipBottom - getPaddingBottom()); - } + Utilities.cancelAnimation(mTransformAnimation); - // Update the task progress - Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator); - if (duration <= 0) { + // Compose the animations for the transform + mTmpAnimators.clear(); + boolean requiresHwLayers = toTransform.applyToTaskView(this, mTmpAnimators, toAnimation, + !config.fakeShadows); + if (toAnimation.isImmediate()) { + mThumbnailView.setBitmapScale(toTransform.thumbnailScale); setTaskProgress(toTransform.p); + if (toAnimation.listener != null) { + toAnimation.listener.onAnimationEnd(null); + } } else { - mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p); - mTaskProgressAnimator.setDuration(duration); - mTaskProgressAnimator.addUpdateListener(mUpdateDimListener); - mTaskProgressAnimator.start(); + if (Float.compare(mThumbnailView.getBitmapScale(), toTransform.thumbnailScale) != 0) { + mTmpAnimators.add(ObjectAnimator.ofFloat(mThumbnailView, + TaskViewThumbnail.BITMAP_SCALE, mThumbnailView.getBitmapScale(), + toTransform.thumbnailScale)); + } + if (Float.compare(getTaskProgress(), toTransform.p) != 0) { + mTmpAnimators.add(ObjectAnimator.ofFloat(this, TASK_PROGRESS, getTaskProgress(), + toTransform.p)); + } + ValueAnimator updateCallbackAnim = ValueAnimator.ofInt(0, 1); + updateCallbackAnim.addUpdateListener(updateCallback); + mTmpAnimators.add(updateCallbackAnim); + + // Create the animator + mTransformAnimation = toAnimation.createAnimator(mTmpAnimators); + if (requiresHwLayers) { + setLayerType(View.LAYER_TYPE_HARDWARE, null); + mTransformAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + setLayerType(View.LAYER_TYPE_NONE, null); + } + }); + } + mTransformAnimation.start(); } } /** Resets this view's properties */ void resetViewProperties() { + Utilities.cancelAnimation(mTransformAnimation); setDim(0); setVisibility(View.VISIBLE); getViewBounds().reset(); TaskViewTransform.reset(this); - if (mActionButtonView != null) { - mActionButtonView.setScaleX(1f); - mActionButtonView.setScaleY(1f); - mActionButtonView.setAlpha(1f); - mActionButtonView.setTranslationZ(mActionButtonTranslationZ); - } - } - - /** Prepares this task view for the enter-recents animations. This is called earlier in the - * first layout because the actual animation into recents may take a long time. */ - void prepareEnterRecentsAnimation(boolean hideTask, boolean occludesLaunchTarget, - int offscreenY) { - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); - int initialDim = getDim(); - if (hideTask) { - setVisibility(View.INVISIBLE); - } else if (launchState.launchedHasConfigurationChanged) { - // Just load the views as-is - } else if (launchState.launchedFromAppWithThumbnail) { - if (mTask.isLaunchTarget) { - // Set the dim to 0 so we can animate it in - initialDim = 0; - // Hide the action button - mActionButtonView.setAlpha(0f); - } else if (occludesLaunchTarget) { - // Move the task view off screen (below) so we can animate it in - setTranslationY(offscreenY); - } - - } else if (launchState.launchedFromHome) { - // Move the task view off screen (below) so we can animate it in - setTranslationY(offscreenY); - setTranslationZ(0); - setScaleX(1f); - setScaleY(1f); - } - // Apply the current dim - setDim(initialDim); - } - - /** Animates this task view as it enters recents */ - void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); - Resources res = mContext.getResources(); - final TaskViewTransform transform = ctx.currentTaskTransform; - final int taskViewEnterFromAppDuration = res.getInteger( - R.integer.recents_task_enter_from_app_duration); - final int taskViewEnterFromHomeDuration = res.getInteger( - R.integer.recents_task_enter_from_home_duration); - final int taskViewEnterFromHomeStaggerDelay = res.getInteger( - R.integer.recents_task_enter_from_home_stagger_delay); - final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( - R.dimen.recents_task_view_affiliate_group_enter_offset); - - if (launchState.launchedFromAppWithThumbnail) { - if (mTask.isLaunchTarget) { - ctx.postAnimationTrigger.increment(); - // Start the dim animation - animateDimToProgress(taskViewEnterFromAppDuration, - ctx.postAnimationTrigger.decrementOnAnimationEnd()); - - // Start the action button animation - if (ctx.isScreenPinningEnabled) { - showActionButton(true /* fadeIn */, - taskViewEnterFromAppDuration /* fadeInDuration */); - } - } else { - // Animate the task up if it was occluding the launch target - if (ctx.currentTaskOccludesLaunchTarget) { - setTranslationY(taskViewAffiliateGroupEnterOffset); - setAlpha(0f); - animate().alpha(1f) - .translationY(0) - .setUpdateListener(null) - .setListener(new AnimatorListenerAdapter() { - private boolean hasEnded; - - // We use the animation listener instead of withEndAction() to - // ensure that onAnimationEnd() is called when the animator is - // cancelled - @Override - public void onAnimationEnd(Animator animation) { - if (hasEnded) return; - ctx.postAnimationTrigger.decrement(); - hasEnded = true; - } - }) - .setInterpolator(mFastOutSlowInInterpolator) - .setDuration(taskViewEnterFromHomeDuration) - .start(); - ctx.postAnimationTrigger.increment(); - } - } - - } else if (launchState.launchedFromHome) { - // Animate the tasks up - int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1); - int delay = frontIndex * taskViewEnterFromHomeStaggerDelay; - - setScaleX(transform.scale); - setScaleY(transform.scale); - if (!config.fakeShadows) { - animate().translationZ(transform.translationZ); - } - animate() - .translationY(0) - .setStartDelay(delay) - .setUpdateListener(ctx.updateListener) - .setListener(new AnimatorListenerAdapter() { - private boolean hasEnded; - - // We use the animation listener instead of withEndAction() to ensure that - // onAnimationEnd() is called when the animator is cancelled - @Override - public void onAnimationEnd(Animator animation) { - if (hasEnded) return; - ctx.postAnimationTrigger.decrement(); - hasEnded = true; - } - }) - .setInterpolator(mQuintOutInterpolator) - .setDuration(taskViewEnterFromHomeDuration + - frontIndex * taskViewEnterFromHomeStaggerDelay) - .start(); - ctx.postAnimationTrigger.increment(); - } - } - - public void cancelEnterRecentsAnimation() { - animate().cancel(); - } - - /** Animates this task view as it leaves recents by pressing home. */ - void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { - int taskViewExitToHomeDuration = getResources().getInteger( - R.integer.recents_task_exit_to_home_duration); - animate() - .translationY(ctx.offscreenTranslationY) - .setStartDelay(0) - .setUpdateListener(null) - .setListener(null) - .setInterpolator(mFastOutLinearInInterpolator) - .setDuration(taskViewExitToHomeDuration) - .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) - .start(); - ctx.postAnimationTrigger.increment(); - } - - /** Animates this task view as it exits recents */ - void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, - boolean occludesLaunchTarget, boolean lockToTask) { - final int taskViewExitToAppDuration = mContext.getResources().getInteger( - R.integer.recents_task_exit_to_app_duration); - final int taskViewAffiliateGroupEnterOffset = mContext.getResources().getDimensionPixelSize( - R.dimen.recents_task_view_affiliate_group_enter_offset); - - if (isLaunchingTask) { - // Animate the dim - if (mDimAlpha > 0) { - ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0); - anim.setDuration(taskViewExitToAppDuration); - anim.setInterpolator(mFastOutLinearInInterpolator); - anim.start(); - } - // Animate the action button away - if (!lockToTask) { - float toScale = 0.9f; - mActionButtonView.animate() - .scaleX(toScale) - .scaleY(toScale); - } - mActionButtonView.animate() - .alpha(0f) - .setStartDelay(0) - .setDuration(taskViewExitToAppDuration) - .setInterpolator(PhoneStatusBar.ALPHA_OUT) - .withEndAction(postAnimRunnable) - .withLayer() - .start(); - } else { - // Hide the dismiss button - mHeaderView.startLaunchTaskDismissAnimation(postAnimRunnable); - // If this is another view in the task grouping and is in front of the launch task, - // animate it away first - if (occludesLaunchTarget) { - animate().alpha(0f) - .translationY(getTranslationY() + taskViewAffiliateGroupEnterOffset) - .setStartDelay(0) - .setUpdateListener(null) - .setListener(null) - .setInterpolator(mFastOutLinearInInterpolator) - .setDuration(taskViewExitToAppDuration) - .start(); - } - } + mActionButtonView.setScaleX(1f); + mActionButtonView.setScaleY(1f); + mActionButtonView.setAlpha(1f); + mActionButtonView.setTranslationZ(mActionButtonTranslationZ); } - /** Animates the deletion of this task view */ - void startDeleteTaskAnimation(final Runnable r, int delay) { - int taskViewRemoveAnimDuration = getResources().getInteger( - R.integer.recents_animate_task_view_remove_duration); - int taskViewRemoveAnimTranslationXPx = getResources().getDimensionPixelSize( - R.dimen.recents_task_view_remove_anim_translation_x); - - // Disabling clipping with the stack while the view is animating away - setClipViewInStack(false); - - animate().translationX(taskViewRemoveAnimTranslationXPx) - .alpha(0f) - .setStartDelay(delay) - .setUpdateListener(null) - .setListener(null) - .setInterpolator(mFastOutSlowInInterpolator) - .setDuration(taskViewRemoveAnimDuration) - .withEndAction(new Runnable() { - @Override - public void run() { - if (r != null) { - r.run(); - } - - // Re-enable clipping with the stack (we will reuse this view) - setClipViewInStack(true); - } - }) - .start(); + /** + * Cancels any current transform animations. + */ + public void cancelTransformAnimation() { + Utilities.cancelAnimation(mTransformAnimation); } /** Enables/disables handling touch on this task view. */ @@ -600,12 +389,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } } else { float dimAlpha = mDimAlpha / 255.0f; - if (mThumbnailView != null) { - mThumbnailView.setDimAlpha(dimAlpha); - } - if (mHeaderView != null) { - mHeaderView.setDimAlpha(dim); - } + mThumbnailView.setDimAlpha(dimAlpha); + mHeaderView.setDimAlpha(dim); } } @@ -615,18 +400,18 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } /** Animates the dim to the task progress. */ - void animateDimToProgress(int duration, Animator.AnimatorListener postAnimRunnable) { + void animateDimToProgress(int duration, Animator.AnimatorListener animListener) { // Animate the dim into view as well int toDim = getDimFromTaskProgress(); if (toDim != getDim()) { - ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim); + ObjectAnimator anim = ObjectAnimator.ofInt(this, DIM, getDim(), toDim); anim.setDuration(duration); - if (postAnimRunnable != null) { - anim.addListener(postAnimRunnable); + if (animListener != null) { + anim.addListener(animListener); } anim.start(); } else { - postAnimRunnable.onAnimationEnd(null); + animListener.onAnimationEnd(null); } } @@ -647,14 +432,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, /** * Explicitly sets the focused state of this task. */ - public void setFocusedState(boolean isFocused, boolean animated, boolean requestViewFocus) { - if (DEBUG) { - Log.d(TAG, "setFocusedState: " + mTask.title + " focused: " + isFocused + - " animated: " + animated + " requestViewFocus: " + requestViewFocus + - " isFocused(): " + isFocused() + - " isAccessibilityFocused(): " + isAccessibilityFocused()); - } - + public void setFocusedState(boolean isFocused, boolean requestViewFocus) { SystemServicesProxy ssp = Recents.getSystemServices(); if (isFocused) { if (requestViewFocus && !isFocused()) { @@ -681,28 +459,101 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, if (fadeIn) { if (mActionButtonView.getAlpha() < 1f) { - mActionButtonView.setAlpha(0f); - mActionButtonView.animate().alpha(1f) + mActionButtonView.animate() + .alpha(1f) + .scaleX(1f) + .scaleY(1f) .setDuration(fadeInDuration) .setInterpolator(PhoneStatusBar.ALPHA_IN) .withLayer() .start(); } } else { + mActionButtonView.setScaleX(1f); + mActionButtonView.setScaleY(1f); mActionButtonView.setAlpha(1f); + mActionButtonView.setTranslationZ(mActionButtonTranslationZ); } } /** * Immediately hides the action button. + * + * @param fadeOut whether or not to animate the action button out. */ - public void hideActionButton() { - mActionButtonView.setVisibility(View.INVISIBLE); + public void hideActionButton(boolean fadeOut, int fadeOutDuration, boolean scaleDown, + final Animator.AnimatorListener animListener) { + if (fadeOut) { + if (mActionButtonView.getAlpha() > 0f) { + if (scaleDown) { + float toScale = 0.9f; + mActionButtonView.animate() + .scaleX(toScale) + .scaleY(toScale); + } + mActionButtonView.animate() + .alpha(0f) + .setDuration(fadeOutDuration) + .setInterpolator(PhoneStatusBar.ALPHA_OUT) + .withEndAction(new Runnable() { + @Override + public void run() { + if (animListener != null) { + animListener.onAnimationEnd(null); + } + mActionButtonView.setVisibility(View.INVISIBLE); + } + }) + .withLayer() + .start(); + } + } else { + mActionButtonView.setAlpha(0f); + mActionButtonView.setVisibility(View.INVISIBLE); + if (animListener != null) { + animListener.onAnimationEnd(null); + } + } + } + + /**** TaskStackAnimationHelper.Callbacks Implementation ****/ + + @Override + public void onPrepareLaunchTargetForEnterAnimation() { + // These values will be animated in when onStartLaunchTargetEnterAnimation() is called + setDim(0); + mActionButtonView.setAlpha(0f); + } + + @Override + public void onStartLaunchTargetEnterAnimation(int duration, boolean screenPinningEnabled, + ReferenceCountedTrigger postAnimationTrigger) { + postAnimationTrigger.increment(); + animateDimToProgress(duration, postAnimationTrigger.decrementOnAnimationEnd()); + + if (screenPinningEnabled) { + showActionButton(true /* fadeIn */, duration /* fadeInDuration */); + } + } + + @Override + public void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested, + ReferenceCountedTrigger postAnimationTrigger) { + if (mDimAlpha > 0) { + ObjectAnimator anim = ObjectAnimator.ofInt(this, DIM, getDim(), 0); + anim.setDuration(duration); + anim.setInterpolator(PhoneStatusBar.ALPHA_OUT); + anim.start(); + } + + postAnimationTrigger.increment(); + hideActionButton(true /* fadeOut */, duration, + !screenPinningRequested /* scaleDown */, + postAnimationTrigger.decrementOnAnimationEnd()); } /**** TaskCallbacks Implementation ****/ - /** Binds this task view to the task */ public void onTaskBound(Task t) { mTask = t; mTask.addCallback(this); @@ -710,22 +561,18 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, @Override public void onTaskDataLoaded(Task task) { - if (mThumbnailView != null && mHeaderView != null) { - // Bind each of the views to the new task data - mThumbnailView.rebindToTask(mTask); - mHeaderView.rebindToTask(mTask); - } + // Bind each of the views to the new task data + mThumbnailView.rebindToTask(mTask); + mHeaderView.rebindToTask(mTask); mTaskDataLoaded = true; } @Override public void onTaskDataUnloaded() { - if (mThumbnailView != null && mHeaderView != null) { - // Unbind each of the views from the task data and remove the task callback - mTask.removeCallback(this); - mThumbnailView.unbindFromTask(); - mHeaderView.unbindFromTask(); - } + // Unbind each of the views from the task data and remove the task callback + mTask.removeCallback(this); + mThumbnailView.unbindFromTask(); + mHeaderView.unbindFromTask(); mTaskDataLoaded = false; } @@ -761,17 +608,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, // Start listening for drag events setClipViewInStack(false); - // Enlarge the view slightly - final float finalScale = getScaleX() * 1.05f; - animate() - .scaleX(finalScale) - .scaleY(finalScale) - .setDuration(175) - .setUpdateListener(null) - .setListener(null) - .setInterpolator(mFastOutSlowInInterpolator) - .start(); - mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2; mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java new file mode 100644 index 000000000000..0e7f677b9f30 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents.views; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import java.util.List; + +/** + * The animation properties to animate a {@link TaskView} to a given {@TaskViewTransform}. + */ +public class TaskViewAnimation { + + public static final TaskViewAnimation IMMEDIATE = new TaskViewAnimation(0, + new LinearInterpolator()); + + public final int startDelay; + public final int duration; + public final Interpolator interpolator; + public final Animator.AnimatorListener listener; + + public TaskViewAnimation(int duration, Interpolator interpolator) { + this(0 /* startDelay */, duration, interpolator, null); + } + + public TaskViewAnimation(int duration, Interpolator interpolator, + Animator.AnimatorListener listener) { + this(0 /* startDelay */, duration, interpolator, listener); + } + + public TaskViewAnimation(int startDelay, int duration, Interpolator interpolator, + Animator.AnimatorListener listener) { + this.startDelay = startDelay; + this.duration = duration; + this.interpolator = interpolator; + this.listener = listener; + } + + /** + * Creates a new {@link AnimatorSet} that will animate the given animators with the current + * animation properties. + */ + public AnimatorSet createAnimator(List animators) { + AnimatorSet anim = new AnimatorSet(); + anim.setStartDelay(startDelay); + anim.setDuration(duration); + anim.setInterpolator(interpolator); + if (listener != null) { + anim.addListener(listener); + } + anim.playTogether(animators); + return anim; + } + + /** + * Returns whether this animation has any duration. + */ + public boolean isImmediate() { + return duration <= 0; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 9a2ffe712248..e8b7574b0ffb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -291,22 +291,6 @@ public class TaskViewHeader extends FrameLayout mMoveTaskButton.setOnClickListener(null); } - /** Animates this task bar dismiss button when launching a task. */ - void startLaunchTaskDismissAnimation(final Runnable postAnimationRunanble) { - if (mDismissButton.getVisibility() == View.VISIBLE) { - int taskViewExitToAppDuration = mContext.getResources().getInteger( - R.integer.recents_task_exit_to_app_duration); - mDismissButton.animate().cancel(); - mDismissButton.animate() - .alpha(0f) - .setStartDelay(0) - .setInterpolator(mFastOutSlowInInterpolator) - .setDuration(taskViewExitToAppDuration) - .withEndAction(postAnimationRunanble) - .start(); - } - } - /** Animates this task bar if the user does not interact with the stack after a certain time. */ void startNoUserInteractionAnimation() { if (mDismissButton.getVisibility() != View.VISIBLE) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index 3ee50ac1cafa..14bab6462da3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -16,16 +16,19 @@ package com.android.systemui.recents.views; -import android.animation.ValueAnimator; +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; import android.graphics.RectF; import android.util.IntProperty; import android.util.Property; import android.view.View; -import android.view.ViewPropertyAnimator; -import android.view.animation.Interpolator; +import java.util.ArrayList; -/* The transform state for a task view */ +/** + * The visual properties for a {@link TaskView}. + */ public class TaskViewTransform { public static final Property LEFT = @@ -80,9 +83,6 @@ public class TaskViewTransform { } }; - // TODO: Move this out of the transform - public int startDelay = 0; - public float translationZ = 0; public float scale = 1f; public float alpha = 1f; @@ -94,15 +94,10 @@ public class TaskViewTransform { // This is a window-space rect used for positioning the task in the stack and freeform workspace public RectF rect = new RectF(); - public TaskViewTransform() { - // Do nothing - } - /** * Resets the current transform. */ public void reset() { - startDelay = 0; translationZ = 0; scale = 1f; alpha = 1f; @@ -116,83 +111,83 @@ public class TaskViewTransform { public boolean hasAlphaChangedFrom(float v) { return (Float.compare(alpha, v) != 0); } + public boolean hasScaleChangedFrom(float v) { return (Float.compare(scale, v) != 0); } + public boolean hasTranslationZChangedFrom(float v) { return (Float.compare(translationZ, v) != 0); } - /** Applies this transform to a view. */ - public void applyToTaskView(TaskView v, int duration, Interpolator interp, boolean allowLayers, - boolean allowShadows, ValueAnimator.AnimatorUpdateListener updateCallback) { - // Check to see if any properties have changed, and update the task view - if (duration > 0) { - ViewPropertyAnimator anim = v.animate(); - boolean requiresLayers = false; + public boolean hasRectChangedFrom(View v) { + return ((int) rect.left != v.getLeft()) || ((int) rect.right != v.getRight()) || + ((int) rect.top != v.getTop()) || ((int) rect.bottom != v.getBottom()); + } + + /** + * Applies this transform to a view. + * + * @return whether hardware layers are required for this animation. + */ + public boolean applyToTaskView(TaskView v, ArrayList animators, + TaskViewAnimation taskAnimation, boolean allowShadows) { + // Return early if not visible + boolean requiresHwLayers = false; + if (!visible) { + return requiresHwLayers; + } - // Animate to the final state + if (taskAnimation.isImmediate()) { if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) { - anim.translationZ(translationZ); + v.setTranslationZ(translationZ); } if (hasScaleChangedFrom(v.getScaleX())) { - anim.scaleX(scale) - .scaleY(scale); - requiresLayers = true; + v.setScaleX(scale); + v.setScaleY(scale); } if (hasAlphaChangedFrom(v.getAlpha())) { - // Use layers if we animate alpha - anim.alpha(alpha); - requiresLayers = true; - } - if (requiresLayers && allowLayers) { - anim.withLayer(); + v.setAlpha(alpha); } - if (updateCallback != null) { - anim.setUpdateListener(updateCallback); - } else { - anim.setUpdateListener(null); + if (hasRectChangedFrom(v)) { + v.setLeftTopRightBottom((int) rect.left, (int) rect.top, (int) rect.right, + (int) rect.bottom); } - anim.setListener(null); - anim.setStartDelay(startDelay) - .setDuration(duration) - .setInterpolator(interp) - .start(); } else { - // Set the changed properties if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) { - v.setTranslationZ(translationZ); + animators.add(ObjectAnimator.ofFloat(v, View.TRANSLATION_Z, v.getTranslationZ(), + translationZ)); } if (hasScaleChangedFrom(v.getScaleX())) { - v.setScaleX(scale); - v.setScaleY(scale); + animators.add(ObjectAnimator.ofPropertyValuesHolder(v, + PropertyValuesHolder.ofFloat(View.SCALE_X, v.getScaleX(), scale), + PropertyValuesHolder.ofFloat(View.SCALE_Y, v.getScaleX(), scale))); } if (hasAlphaChangedFrom(v.getAlpha())) { - v.setAlpha(alpha); + animators.add(ObjectAnimator.ofFloat(v, View.ALPHA, v.getAlpha(), alpha)); + requiresHwLayers = true; + } + if (hasRectChangedFrom(v)) { + animators.add(ObjectAnimator.ofPropertyValuesHolder(v, + PropertyValuesHolder.ofInt(LEFT, v.getLeft(), (int) rect.left), + PropertyValuesHolder.ofInt(TOP, v.getTop(), (int) rect.top), + PropertyValuesHolder.ofInt(RIGHT, v.getRight(), (int) rect.right), + PropertyValuesHolder.ofInt(BOTTOM, v.getBottom(), (int) rect.bottom))); } } + return requiresHwLayers; } /** Reset the transform on a view. */ public static void reset(TaskView v) { - // Cancel any running animations and reset the translation in case something else (like a - // dismiss animation) changes it - v.animate().cancel(); v.setTranslationX(0f); v.setTranslationY(0f); v.setTranslationZ(0f); v.setScaleX(1f); v.setScaleY(1f); v.setAlpha(1f); - v.getViewBounds().setClipBottom(0, false /* forceUpdate */); + v.getViewBounds().setClipBottom(0); v.setLeftTopRightBottom(0, 0, 0, 0); v.mThumbnailView.setBitmapScale(1f); } - - @Override - public String toString() { - return "TaskViewTransform delay: " + startDelay + " z: " + translationZ + - " scale: " + scale + " alpha: " + alpha + " visible: " + visible + - " rect: " + rect + " p: " + p; - } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java deleted file mode 100644 index eaef51c4869a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.views; - -import android.animation.ValueAnimator; -import android.graphics.Rect; -import com.android.systemui.recents.misc.ReferenceCountedTrigger; - -/* Common code related to view animations */ -public class ViewAnimation { - - /* The animation context for a task view animation into Recents */ - public static class TaskViewEnterContext { - // A trigger to run some logic when all the animations complete. This works around the fact - // that it is difficult to coordinate ViewPropertyAnimators - public ReferenceCountedTrigger postAnimationTrigger; - // An update listener to notify as the enter animation progresses (used for the home transition) - ValueAnimator.AnimatorUpdateListener updateListener; - - // These following properties are updated for each task view we start the enter animation on - - // Whether or not screen pinning is enabled - boolean isScreenPinningEnabled; - // Whether or not the current task occludes the launch target - boolean currentTaskOccludesLaunchTarget; - // The task rect for the current stack - Rect currentTaskRect; - // The transform of the current task view - TaskViewTransform currentTaskTransform; - // The view index of the current task view - int currentStackViewIndex; - // The total number of task views - int currentStackViewCount; - - public TaskViewEnterContext(ReferenceCountedTrigger t) { - postAnimationTrigger = t; - } - } - - /* The animation context for a task view animation out of Recents */ - public static class TaskViewExitContext { - // A trigger to run some logic when all the animations complete. This works around the fact - // that it is difficult to coordinate ViewPropertyAnimators - ReferenceCountedTrigger postAnimationTrigger; - - // The translationY to apply to a TaskView to move it off the bottom of the task stack - int offscreenTranslationY; - - public TaskViewExitContext(ReferenceCountedTrigger t) { - postAnimationTrigger = t; - } - } - -} -- cgit v1.2.3-59-g8ed1b