diff options
| author | 2016-01-07 23:05:34 +0000 | |
|---|---|---|
| committer | 2016-01-07 23:05:34 +0000 | |
| commit | b4f4dc4e4db5fd65cd457eecd3c98cfa11367615 (patch) | |
| tree | c8e0cf78904c61ea841ff915381ef0ac63615b5c | |
| parent | 3b8e5a1c951c5598a367e2edfdb23d2da186611f (diff) | |
| parent | f24f21695f5609d06402cf61e3500d408b99bdcb (diff) | |
Merge "Refactoring and unifying TaskView animations."
20 files changed, 1086 insertions, 999 deletions
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 @@ <integer name="recents_nav_bar_scrim_enter_duration">400</integer> <!-- The animation duration for animating the removal of a task view. --> - <integer name="recents_animate_task_view_remove_duration">250</integer> + <integer name="recents_animate_task_view_remove_duration">175</integer> <!-- The animation duration for scrolling the stack to a particular item. --> <integer name="recents_animate_task_stack_scroll_duration">200</integer> 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 @@ -88,6 +88,15 @@ public class Utilities { } /** + * 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<Task> 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 @@ -564,6 +564,22 @@ public class TaskStack { } /** + * Returns the set of "freeform" tasks in the stack. + */ + public ArrayList<Task> getFreeformTasks() { + ArrayList<Task> freeformTasks = new ArrayList<>(); + ArrayList<Task> 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. */ public ArrayList<Task> computeAllTasksList() { 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<AnimateableViewBounds, Integer> CLIP_BOTTOM = - new IntProperty<AnimateableViewBounds>("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 15b4ee3b68a5..f49d98e4e404 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); @@ -491,23 +493,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(); @@ -587,21 +591,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(); + } + }); + } } /** @@ -615,20 +621,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<TaskView> 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<TaskView> 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<TaskView> 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<TaskView> 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<TaskView> 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<TaskView> 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, Integer> DRAWABLE_ALPHA = new IntProperty<Drawable>("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<TaskView, Task> mViewPool; + + ArrayList<TaskView> mTaskViews = new ArrayList<>(); ArrayList<TaskViewTransform> 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<Task, TaskView> mTmpTaskViewMap = new HashMap<>(); - ArrayList<TaskView> mTaskViews = new ArrayList<>(); - List<TaskView> mImmutableTaskViews = new ArrayList<>(); List<TaskView> 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<TaskView> 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<Task> 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<Task> tasks = mStack.getStackTasks(); + final boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms, tasks, + stackScroll, visibleStackRange); + + // Return all the invisible children to the pool + mTmpTaskViewMap.clear(); + final List<TaskView> 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<TaskView> 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<TaskView> 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<TaskView> 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<TaskView> 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<TaskView> 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<TaskView> 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<TaskView> 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<TaskView> 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<Task> 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<TaskView> 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<TaskView> 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<TaskView, Integer> DIM = + new IntProperty<TaskView>("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<TaskView, Float> TASK_PROGRESS = + new FloatProperty<TaskView>("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<Animator> 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<Animator> 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<View, Integer> 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<Animator> 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; - } - } - -} |