Merge "Refactoring and unifying TaskView animations."
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 40e8b50..955af82 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 992afb4..e7be858 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 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 90eaca7..2637d88 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 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
-import android.util.Log;
import java.util.ArrayList;
@@ -107,16 +105,20 @@
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 2bf2ccb..086fb58 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 @@
}
/**
+ * 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 d6262ac..d8dfce5 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.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 @@
* - 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 d030fc1..f7e2b9d 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 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 5e720cb..bae4784 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 @@
}
/**
+ * 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 c0b8a9d..b8bbf51 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 @@
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 @@
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 @@
}
/** 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 @@
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 15b4ee3..f49d98e 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.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 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 @@
// Handle the case where we drop onto a dock region
if (event.dropTarget instanceof TaskStack.DockState) {
- final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+ TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+ TaskStackLayoutAlgorithm stackLayout = mTaskStackView.getStackAlgorithm();
+ TaskStackViewScroller stackScroller = mTaskStackView.getScroller();
+ TaskViewTransform tmpTransform = new TaskViewTransform();
- // 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();
+ // 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 @@
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 @@
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 318801d..5fbf421 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.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 @@
*/
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 @@
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 0000000..787e330
--- /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 901799e..1ee22e9 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.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 @@
}
Context mContext;
- private TaskStackView mStackView;
private Interpolator mLinearOutSlowInInterpolator;
private StackState mState = StackState.SPLIT;
@@ -277,9 +275,12 @@
// 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 void setFocusState(float focusState) {
mFocusState = focusState;
- mStackView.requestSynchronizeStackViewsWithModel();
+ updateFrontBackTransforms();
}
/**
@@ -365,6 +366,7 @@
mUnfocusedCurveInterpolator = new FreePathInterpolator(mUnfocusedCurve);
mFocusedCurve = constructFocusedCurve();
mFocusedCurveInterpolator = new FreePathInterpolator(mFocusedCurve);
+ updateFrontBackTransforms();
}
/**
@@ -453,7 +455,7 @@
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 @@
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 @@
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 ba67ad8..33ceaa1 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.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.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 @@
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 @@
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 @@
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- requestUpdateStackViewsClip();
+ mTaskViewsClipDirty = true;
+ invalidate();
}
};
@@ -190,10 +190,11 @@
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 @@
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 @@
// 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 @@
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 @@
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;
- // 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);
+ // 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);
+
+ 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);
-
- // Update the task views list after adding the new task view
- updateTaskViewsList();
- }
-
- // 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);
- }
- }
- }
-
- // Animate the task into place, the clip for stack tasks will be calculated in
- // clipTaskViews()
- tv.updateViewPropertiesToTaskTransform(transform,
- tv.getViewBounds().getClipBottom(), mStackViewsAnimationDuration,
- mFastOutSlowInInterpolator, mRequestUpdateClippingListener);
- }
-
- // 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 */);
- }
- }
-
- // Reset the request-synchronize params
- mStackViewsAnimationDuration = 0;
- mStackViewsDirty = false;
- mStackViewsClipDirty = true;
- return true;
}
- return false;
+
+ // 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);
+
+ // Skip the invisible non-freeform stack tasks
+ if (i > visibleStackRange[0] && !task.isFreeformTask()) {
+ continue;
+ }
+
+ 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);
+ }
+ }
+ } 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 /* scrollToTask */,
+ true /* requestViewFocus */);
+ } else {
+ setFocusedTask(visibleStackRange[0], false /* scrollToTask */,
+ true /* requestViewFocus */);
+ }
+ }
+ }
+
+ /**
+ * 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();
+ }
}
/**
* 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 @@
}
}
}
- 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 @@
*
* @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 void run() {
TaskView tv = getChildViewForTask(newFocusedTask);
if (tv != null) {
- tv.setFocusedState(true, animated, requestViewFocus);
+ tv.setFocusedState(true, requestViewFocus);
}
}
};
@@ -687,10 +661,7 @@
// 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 @@
}
}
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 @@
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 @@
@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 @@
// 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 @@
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 @@
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 @@
}
}
- /** 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 @@
* 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
@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 @@
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 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 @@
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 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 @@
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 @@
}
});
}
- 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 @@
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 @@
// 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 @@
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 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 56942a8..32f02ac 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 @@
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 @@
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 @@
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 d861d34..5852d07 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 @@
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 3f7b99d..b9fefde 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.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.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.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 @@
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 @@
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 @@
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 @@
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);
+ Utilities.cancelAnimation(mTransformAnimation);
- // 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 */);
+ // Compose the animations for the transform
+ mTmpAnimators.clear();
+ boolean requiresHwLayers = toTransform.applyToTaskView(this, mTmpAnimators, toAnimation,
+ !config.fakeShadows);
+ if (toAnimation.isImmediate()) {
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());
- }
-
- // Update the task progress
- Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator);
- if (duration <= 0) {
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);
- }
+
+ 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();
- }
- }
- }
-
- /** 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 @@
}
} 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 @@
}
/** 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 @@
/**
* 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 @@
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 @@
@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 @@
// 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 0000000..0e7f677
--- /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 9a2ffe7..e8b7574 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 @@
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 3ee50ac..14bab64 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 @@
}
};
- // 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 @@
// 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,50 +111,34 @@
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());
+ }
- // Animate to the final state
- if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
- anim.translationZ(translationZ);
- }
- if (hasScaleChangedFrom(v.getScaleX())) {
- anim.scaleX(scale)
- .scaleY(scale);
- requiresLayers = true;
- }
- if (hasAlphaChangedFrom(v.getAlpha())) {
- // Use layers if we animate alpha
- anim.alpha(alpha);
- requiresLayers = true;
- }
- if (requiresLayers && allowLayers) {
- anim.withLayer();
- }
- if (updateCallback != null) {
- anim.setUpdateListener(updateCallback);
- } else {
- anim.setUpdateListener(null);
- }
- anim.setListener(null);
- anim.setStartDelay(startDelay)
- .setDuration(duration)
- .setInterpolator(interp)
- .start();
- } else {
- // Set the changed properties
+ /**
+ * 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;
+ }
+
+ if (taskAnimation.isImmediate()) {
if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
v.setTranslationZ(translationZ);
}
@@ -170,29 +149,45 @@
if (hasAlphaChangedFrom(v.getAlpha())) {
v.setAlpha(alpha);
}
+ if (hasRectChangedFrom(v)) {
+ v.setLeftTopRightBottom((int) rect.left, (int) rect.top, (int) rect.right,
+ (int) rect.bottom);
+ }
+ } else {
+ if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
+ animators.add(ObjectAnimator.ofFloat(v, View.TRANSLATION_Z, v.getTranslationZ(),
+ translationZ));
+ }
+ if (hasScaleChangedFrom(v.getScaleX())) {
+ 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())) {
+ 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 eaef51c..0000000
--- 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;
- }
- }
-
-}