summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Winson <winsonc@google.com> 2016-01-19 15:07:07 -0800
committer Winson <winsonc@google.com> 2016-01-21 16:05:53 -0800
commit8aa9959413a06c3d2ff75e0c7be9e3cb7ac7cd2e (patch)
treec81e6d47c775077896ea981069e59269ba1dff1d
parentb7a73360b50ede90b4331dd041eef59f002dc131 (diff)
Starting the dismiss animation in parallel with the gesture.
- Introduces notion of ignored tasks for the purposes of layout in TaskStackView. This can be used during drag and drop, and while dismissing to calculate the state of the stack without the task that the user is currently interacting with. - Fixing minor layout issue when the front/back task transforms are improperly calculated when there is a single task - Fixing minor issue when the anchor task is calculated incorrectly when dismissing task views Change-Id: I1eb0864a52e53562e4d573a6ed4f8a5a1615aff9
-rw-r--r--packages/SystemUI/res/values-sw600dp/config.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java449
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java208
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java29
12 files changed, 606 insertions, 210 deletions
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 6795da43e087..17ff19549a0d 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -35,7 +35,7 @@
<!-- Recents: The relative range of visible tasks from the current scroll position
while the stack is focused. -->
- <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+ <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
<item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
<!-- Recents: The relative range of visible tasks from the current scroll position
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e98ec825f4a0..810ca14fc15c 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -194,7 +194,7 @@
<!-- Recents: The relative range of visible tasks from the current scroll position
while the stack is focused. -->
- <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+ <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
<item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
<!-- Recents: The relative range of visible tasks from the current scroll position
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index d28da41e153f..fd4161fe3e48 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -367,6 +367,7 @@ public class SwipeHelper implements Gefingerpoken {
}
anim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
+ updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
mCallback.onChildDismissed(view);
if (endAction != null) {
endAction.run();
@@ -381,9 +382,17 @@ public class SwipeHelper implements Gefingerpoken {
updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
}
});
+ prepareDismissAnimation(animView, anim);
anim.start();
}
+ /**
+ * Called to update the dismiss animation.
+ */
+ protected void prepareDismissAnimation(View view, Animator anim) {
+ // Do nothing
+ }
+
public void snapChild(final View view, float velocity) {
final View animView = mCallback.getChildContentView(view);
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(animView);
@@ -401,14 +410,14 @@ public class SwipeHelper implements Gefingerpoken {
mCallback.onChildSnappedBack(animView);
}
});
- updateSnapBackAnimation(anim);
+ prepareSnapBackAnimation(animView, anim);
anim.start();
}
/**
* Called to update the snap back animation.
*/
- protected void updateSnapBackAnimation(Animator anim) {
+ protected void prepareSnapBackAnimation(View view, Animator anim) {
// Do nothing
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
index 80597bc0bc14..fe23fa07c1e6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -36,6 +36,7 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.TaskViewAnimation;
import java.util.ArrayList;
import java.util.Calendar;
@@ -268,8 +269,8 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd
public void onTaskRemoved(Task task, int position) {
// Since this is removed from the history, we need to update the stack as well to ensure
- // that the model is correct
- mStack.removeTask(task);
+ // that the model is correct. Since the stack is hidden, we can update it immediately.
+ mStack.removeTask(task, TaskViewAnimation.IMMEDIATE);
removeTaskRow(position);
if (mRows.isEmpty()) {
dismissHistory();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java b/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java
new file mode 100644
index 000000000000..72511de9ec80
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java
@@ -0,0 +1,52 @@
+/*
+ * 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.misc;
+
+import android.animation.TypeEvaluator;
+import android.graphics.RectF;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>RectF</code> values.
+ */
+public class RectFEvaluator implements TypeEvaluator<RectF> {
+
+ private RectF mRect = new RectF();
+
+ /**
+ * This function returns the result of linearly interpolating the start and
+ * end Rect values, with <code>fraction</code> representing the proportion
+ * between the start and end values. The calculation is a simple parametric
+ * calculation on each of the separate components in the Rect objects
+ * (left, top, right, and bottom).
+ *
+ * <p>The object returned will be the <code>reuseRect</code> passed into the constructor.</p>
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start Rect
+ * @param endValue The end Rect
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ @Override
+ public RectF evaluate(float fraction, RectF startValue, RectF endValue) {
+ float left = startValue.left + ((endValue.left - startValue.left) * fraction);
+ float top = startValue.top + ((endValue.top - startValue.top) * fraction);
+ float right = startValue.right + ((endValue.right - startValue.right) * fraction);
+ float bottom = startValue.bottom + ((endValue.bottom - startValue.bottom) * fraction);
+ mRect.set(left, top, right, bottom);
+ return mRect;
+ }
+}
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 327cdf87994d..ad723e23e671 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -42,6 +42,7 @@ import com.android.systemui.recents.misc.NamedCounter;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.views.DropTarget;
+import com.android.systemui.recents.views.TaskViewAnimation;
import java.util.ArrayList;
import java.util.Collections;
@@ -226,12 +227,12 @@ public class TaskStack {
* Notifies when a task has been removed from the stack.
*/
void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
- Task newFrontMostTask);
+ Task newFrontMostTask, TaskViewAnimation animation);
/**
* Notifies when a task has been removed from the history.
*/
- void onHistoryTaskRemoved(TaskStack stack, Task removedTask);
+ void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation);
}
/**
@@ -520,21 +521,24 @@ public class TaskStack {
}
}
- /** Removes a task */
- public void removeTask(Task t) {
+ /**
+ * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
+ * how they should update themselves.
+ */
+ public void removeTask(Task t, TaskViewAnimation animation) {
if (mStackTaskList.contains(t)) {
boolean wasFrontMostTask = (getStackFrontMostTask() == t);
removeTaskImpl(mStackTaskList, t);
Task newFrontMostTask = getStackFrontMostTask();
if (mCb != null) {
// Notify that a task has been removed
- mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask);
+ mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask, animation);
}
} else if (mHistoryTaskList.contains(t)) {
removeTaskImpl(mHistoryTaskList, t);
if (mCb != null) {
// Notify that a task has been removed
- mCb.onHistoryTaskRemoved(this, t);
+ mCb.onHistoryTaskRemoved(this, t, animation);
}
}
mRawTaskList.remove(t);
@@ -564,7 +568,8 @@ public class TaskStack {
Task task = mRawTaskList.get(i);
if (!newTasksMap.containsKey(task.key)) {
if (notifyStackChanges) {
- mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null);
+ mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null,
+ TaskViewAnimation.IMMEDIATE);
}
}
task.setGroup(null);
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 e727652f5582..e448101577de 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -513,7 +513,7 @@ public class RecentsView extends FrameLayout {
taskViewRect.right, taskViewRect.bottom);
// Remove the task view after it is docked
- mTaskStackView.updateLayout(false /* boundScroll */);
+ mTaskStackView.updateLayoutAlgorithm(false /* boundScroll */);
stackLayout.getStackTransform(event.task, stackScroller.getStackScroll(), tmpTransform,
null);
tmpTransform.alpha = 0;
@@ -529,11 +529,14 @@ public class RecentsView extends FrameLayout {
ssp.startTaskInDockedMode(getContext(), event.taskView,
event.task.key.id, dockState.createMode);
- mTaskStackView.getStack().removeTask(event.task);
+ // Animate the stack accordingly
+ TaskViewAnimation stackAnim = new TaskViewAnimation(
+ TaskStackView.DEFAULT_SYNC_STACK_DURATION,
+ mFastOutSlowInInterpolator);
+ mTaskStackView.getStack().removeTask(event.task, stackAnim);
}
}));
-
MetricsLogger.action(mContext,
MetricsLogger.ACTION_WINDOW_DOCK_DRAG_DROP);
} else {
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 68ff63c1507c..5bd5a8159803 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -373,7 +373,7 @@ public class TaskStackLayoutAlgorithm {
* Computes the minimum and maximum scroll progress values and the progress values for each task
* in the stack.
*/
- void update(TaskStack stack, ArraySet<Task> ignoreTasksSet) {
+ void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Clear the progress map
@@ -393,7 +393,7 @@ public class TaskStackLayoutAlgorithm {
ArrayList<Task> stackTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
- if (ignoreTasksSet.contains(task)) {
+ if (ignoreTasksSet.contains(task.key)) {
continue;
}
if (task.isFreeformTask()) {
@@ -553,7 +553,8 @@ public class TaskStackLayoutAlgorithm {
boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
if (isFrontMostTaskInGroup) {
- getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null);
+ getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null,
+ false /* ignoreSingleTaskCase */);
float screenY = tmpTransform.rect.top;
boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
if (hasVisibleThumbnail) {
@@ -596,13 +597,21 @@ public class TaskStackLayoutAlgorithm {
return transformOut;
}
return getStackTransform(mTaskIndexMap.get(task.key), stackScroll, transformOut,
- frontTransform);
+ frontTransform, false /* ignoreSingleTaskCase */);
}
}
- /** Update/get the transform */
+ /**
+ * Update/get the transform.
+ *
+ * @param ignoreSingleTaskCase When set, will ensure that the transform computed does not take
+ * into account the special single-task case. This is only used
+ * internally to ensure that we can calculate the transform for any
+ * position in the stack.
+ */
public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
- TaskViewTransform transformOut, TaskViewTransform frontTransform) {
+ TaskViewTransform transformOut, TaskViewTransform frontTransform,
+ boolean ignoreSingleTaskCase) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Compute the focused and unfocused offset
@@ -632,7 +641,7 @@ public class TaskStackLayoutAlgorithm {
int y;
float z;
float relP;
- if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+ if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
// When there is exactly one task, then decouple the task from the stack and just move
// in screen space
p = (mMinScrollP - stackScroll) / mNumStackTasks;
@@ -762,8 +771,8 @@ public class TaskStackLayoutAlgorithm {
mFocusState * (mFocusedRange.relativeMin - mUnfocusedRange.relativeMin);
float max = mUnfocusedRange.relativeMax +
mFocusState * (mFocusedRange.relativeMax - mUnfocusedRange.relativeMax);
- getStackTransform(min, 0f, mBackOfStackTransform, null);
- getStackTransform(max, 0f, mFrontOfStackTransform, null);
+ getStackTransform(min, 0f, mBackOfStackTransform, null, true /* ignoreSingleTaskCase */);
+ getStackTransform(max, 0f, mFrontOfStackTransform, null, true /* ignoreSingleTaskCase */);
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 7583a19308f2..3b78d71c3a5c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -30,6 +30,7 @@ import android.os.Parcelable;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.MutableBoolean;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -77,7 +78,6 @@ import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -100,10 +100,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
private static final float SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
- private static final int DEFAULT_SYNC_STACK_DURATION = 200;
+ public 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;
+ private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>();
+
TaskStack mStack;
TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewScroller mStackScroller;
@@ -116,7 +118,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
ArrayList<TaskView> mTaskViews = new ArrayList<>();
ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
- TaskViewAnimation mDeferredTaskViewUpdateAnimation = null;
+ ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>();
+ TaskViewAnimation mDeferredTaskViewLayoutAnimation = null;
DozeTrigger mUIDozeTrigger;
Task mFocusedTask;
@@ -137,7 +140,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int[] mTmpVisibleRange = new int[2];
Rect mTmpRect = new Rect();
ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
- ArraySet<Task> mTmpTaskSet = new ArraySet<>();
List<TaskView> mTmpTaskViews = new ArrayList<>();
TaskViewTransform mTmpTransform = new TaskViewTransform();
LayoutInflater mInflater;
@@ -345,30 +347,82 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
- * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
+ * Adds a task to the ignored set.
+ */
+ void addIgnoreTask(Task task) {
+ mIgnoreTasks.add(task.key);
+ }
+
+ /**
+ * Removes a task from the ignored set.
+ */
+ void removeIgnoreTask(Task task) {
+ mIgnoreTasks.remove(task.key);
+ }
+
+ /**
+ * Returns whether the specified {@param task} is ignored.
+ */
+ boolean isIgnoredTask(Task task) {
+ return mIgnoreTasks.contains(task.key);
+ }
+
+ /**
+ * Computes the task transforms at the current stack scroll for all visible tasks. If a valid
+ * target stack scroll is provided (ie. is different than {@param curStackScroll}), then the
+ * visible range includes all tasks at the target stack scroll. This is useful for ensure that
+ * all views necessary for a transition or animation will be visible at the start.
+ *
* This call ignores freeform tasks.
+ *
+ * @param taskTransforms The set of task view transforms to reuse, this list will be sized to
+ * match the size of {@param tasks}
+ * @param tasks The set of tasks for which to generate transforms
+ * @param curStackScroll The current stack scroll
+ * @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to.
+ * The range of the union of the visible views at the current and
+ * target stack scrolls will be returned.
+ * @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range.
+ * Transforms will still be calculated for the ignore tasks.
*/
- private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
- ArrayList<Task> tasks, float stackScroll,
- int[] visibleRangeOut, ArraySet<Task> ignoreTasksSet) {
- int taskTransformCount = taskTransforms.size();
+ boolean computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms,
+ ArrayList<Task> tasks, float curStackScroll, float targetStackScroll,
+ int[] visibleRangeOut, ArraySet<Task.TaskKey> ignoreTasksSet) {
int taskCount = tasks.size();
int frontMostVisibleIndex = -1;
int backMostVisibleIndex = -1;
+ boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
// We can reuse the task transforms where possible to reduce object allocation
Utilities.matchTaskListSize(tasks, taskTransforms);
// Update the stack transforms
TaskViewTransform frontTransform = null;
+ TaskViewTransform frontTransformAtTarget = null;
+ TaskViewTransform transform = null;
+ TaskViewTransform transformAtTarget = null;
for (int i = taskCount - 1; i >= 0; i--) {
Task task = tasks.get(i);
- if (ignoreTasksSet.contains(task)) {
- continue;
- }
- TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
+ // Calculate the current and (if necessary) the target transform for the task
+ transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
taskTransforms.get(i), frontTransform);
+ if (useTargetStackScroll && !transform.visible) {
+ // If we have a target stack scroll and the task is not currently visible, then we
+ // just update the transform at the new scroll
+ // TODO: Optimize this
+ transformAtTarget = mLayoutAlgorithm.getStackTransform(task,
+ targetStackScroll, new TaskViewTransform(), frontTransformAtTarget);
+ if (transformAtTarget.visible) {
+ transform.copyFrom(transformAtTarget);
+ }
+ }
+
+ // For ignore tasks, only calculate the stack transform and skip the calculation of the
+ // visible stack indices
+ if (ignoreTasksSet.contains(task.key)) {
+ continue;
+ }
// For freeform tasks, only calculate the stack transform and skip the calculation of
// the visible stack indices
@@ -392,7 +446,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
break;
}
}
+
frontTransform = transform;
+ frontTransformAtTarget = transformAtTarget;
}
if (visibleRangeOut != null) {
visibleRangeOut[0] = frontMostVisibleIndex;
@@ -402,33 +458,48 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
- * 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.
+ * Binds the visible {@link TaskView}s at the given target scroll.
+ *
+ * @see #bindVisibleTaskViews(float, ArraySet<Task.TaskKey>)
*/
- private void bindTaskViewsWithStack(ArraySet<Task> ignoreTasksSet) {
- final float stackScroll = mStackScroller.getStackScroll();
+ void bindVisibleTaskViews(float targetStackScroll) {
+ bindVisibleTaskViews(targetStackScroll, mIgnoreTasks);
+ }
+
+ /**
+ * Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the
+ * current {@link TaskStack}. This call does not continue on to update their position to the
+ * computed {@link TaskViewTransform}s of the visible range, but only ensures that they will
+ * be added/removed from the view hierarchy and placed in the correct Z order and initial
+ * position (if not currently on screen).
+ *
+ * @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s
+ * includes those visible at the current stack scroll, and all at the
+ * target stack scroll.
+ * @param ignoreTasksSet The set of tasks to ignore in this rebinding of the visible
+ * {@link TaskView}s
+ */
+ void bindVisibleTaskViews(float targetStackScroll, ArraySet<Task.TaskKey> ignoreTasksSet) {
final int[] visibleStackRange = mTmpVisibleRange;
// Get all the task transforms
final ArrayList<Task> tasks = mStack.getStackTasks();
- final boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms,
- tasks, stackScroll, visibleStackRange, ignoreTasksSet);
+ final boolean isValidVisibleRange = computeVisibleTaskTransforms(mCurrentTaskTransforms,
+ tasks, mStackScroller.getStackScroll(), targetStackScroll, visibleStackRange,
+ ignoreTasksSet);
// Return all the invisible children to the pool
- final List<TaskView> taskViews = getTaskViews();
- final int taskViewCount = taskViews.size();
- int lastFocusedTaskIndex = -1;
mTmpTaskViewMap.clear();
- mTmpTaskViewMap.ensureCapacity(tasks.size());
+ List<TaskView> taskViews = getTaskViews();
+ int lastFocusedTaskIndex = -1;
+ int taskViewCount = taskViews.size();
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);
+ TaskView tv = taskViews.get(i);
+ Task task = tv.getTask();
+ int taskIndex = mStack.indexOfStackTask(task);
// Skip ignored tasks
- if (ignoreTasksSet.contains(task)) {
+ if (ignoreTasksSet.contains(task.key)) {
continue;
}
@@ -445,13 +516,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Pick up all the newly visible children
- int lastVisStackIndex = isValidVisibleStackRange ? visibleStackRange[1] : 0;
- for (int i = mStack.getTaskCount() - 1; i >= lastVisStackIndex; i--) {
- final Task task = tasks.get(i);
- final TaskViewTransform transform = mCurrentTaskTransforms.get(i);
+ int lastVisStackIndex = isValidVisibleRange ? visibleStackRange[1] : 0;
+ for (int i = tasks.size() - 1; i >= lastVisStackIndex; i--) {
+ Task task = tasks.get(i);
+ TaskViewTransform transform = mCurrentTaskTransforms.get(i);
// Skip ignored tasks
- if (ignoreTasksSet.contains(task)) {
+ if (ignoreTasksSet.contains(task.key)) {
continue;
}
@@ -502,26 +573,34 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
- * Cancels any existing {@link TaskView} animations, and updates each {@link TaskView} to its
- * current position as defined by the {@link TaskStackLayoutAlgorithm}.
+ * Relayout the the visible {@link TaskView}s to their current transforms as specified by the
+ * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
+ * animations that are current running on those task views, and will ensure that the children
+ * {@link TaskView}s will match the set of visible tasks in the stack.
*
- * @param ignoreTasks the set of tasks to ignore in the relayout
+ * @see #relayoutTaskViews(TaskViewAnimation, ArraySet<Task.TaskKey>)
*/
- private void updateTaskViewsToLayout(TaskViewAnimation animation, Task... ignoreTasks) {
- // Keep track of the ignore tasks
- ArraySet<Task> ignoreTasksSet = mTmpTaskSet;
- ignoreTasksSet.clear();
- ignoreTasksSet.ensureCapacity(ignoreTasks.length);
- Collections.addAll(ignoreTasksSet, ignoreTasks);
+ void relayoutTaskViews(TaskViewAnimation animation) {
+ relayoutTaskViews(animation, mIgnoreTasks);
+ }
+ /**
+ * Relayout the the visible {@link TaskView}s to their current transforms as specified by the
+ * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
+ * animations that are current running on those task views, and will ensure that the children
+ * {@link TaskView}s will match the set of visible tasks in the stack.
+ *
+ * @param ignoreTasksSet the set of tasks to ignore in the relayout
+ */
+ void relayoutTaskViews(TaskViewAnimation animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
// If we had a deferred animation, cancel that
- mDeferredTaskViewUpdateAnimation = null;
+ mDeferredTaskViewLayoutAnimation = null;
// Cancel all task view animations
cancelAllTaskViewAnimations();
- // Fetch the current set of TaskViews
- bindTaskViewsWithStack(ignoreTasksSet);
+ // Synchronize the current set of TaskViews
+ bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTasksSet);
// Animate them to their final transforms with the given animation
List<TaskView> taskViews = getTaskViews();
@@ -531,7 +610,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
final int taskIndex = mStack.indexOfStackTask(tv.getTask());
final TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
- if (ignoreTasksSet.contains(tv.getTask())) {
+ if (ignoreTasksSet.contains(tv.getTask().key)) {
continue;
}
@@ -542,8 +621,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/**
* Posts an update to synchronize the {@link TaskView}s with the stack on the next frame.
*/
- private void updateTaskViewsToLayoutOnNextFrame(TaskViewAnimation animation) {
- mDeferredTaskViewUpdateAnimation = animation;
+ void relayoutTaskViewsOnNextFrame(TaskViewAnimation animation) {
+ mDeferredTaskViewLayoutAnimation = animation;
postInvalidateOnAnimation();
}
@@ -558,13 +637,62 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
+ * Returns the current task transforms of all tasks, falling back to the stack layout if there
+ * is no {@link TaskView} for the task.
+ */
+ public void getCurrentTaskTransforms(ArrayList<Task> tasks,
+ ArrayList<TaskViewTransform> transformsOut) {
+ Utilities.matchTaskListSize(tasks, transformsOut);
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ Task task = tasks.get(i);
+ TaskViewTransform transform = transformsOut.get(i);
+ TaskView tv = getChildViewForTask(task);
+ if (tv != null) {
+ transform.fillIn(tv);
+ } else {
+ mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
+ transform, null);
+ }
+ transform.visible = true;
+ }
+ }
+
+ /**
+ * Returns the task transforms for all the tasks in the stack if the stack was at the given
+ * {@param stackScroll}.
+ */
+ public void getLayoutTaskTransforms(float stackScroll, ArrayList<Task> tasks,
+ ArrayList<TaskViewTransform> transformsOut) {
+ Utilities.matchTaskListSize(tasks, transformsOut);
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ Task task = tasks.get(i);
+ TaskViewTransform transform = transformsOut.get(i);
+ mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null);
+ transform.visible = true;
+ }
+ }
+
+ /**
+ * Cancels all {@link TaskView} animations.
+ *
+ * @see #cancelAllTaskViewAnimations(ArraySet<Task.TaskKey>)
+ */
+ void cancelAllTaskViewAnimations() {
+ cancelAllTaskViewAnimations(mIgnoreTasks);
+ }
+
+ /**
* Cancels all {@link TaskView} animations.
+ *
+ * @param ignoreTasksSet The set of tasks to continue running their animations.
*/
- private void cancelAllTaskViewAnimations() {
+ void cancelAllTaskViewAnimations(ArraySet<Task.TaskKey> ignoreTasksSet) {
List<TaskView> taskViews = getTaskViews();
for (int i = taskViews.size() - 1; i >= 0; i--) {
final TaskView tv = taskViews.get(i);
- tv.cancelTransformAnimation();
+ if (!ignoreTasksSet.contains(tv.getTask().key)) {
+ tv.cancelTransformAnimation();
+ }
}
}
@@ -577,11 +705,22 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Update the clip on each task child
List<TaskView> taskViews = getTaskViews();
TaskView tmpTv = null;
+ TaskView prevVisibleTv = null;
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
TaskView tv = taskViews.get(i);
TaskView frontTv = null;
int clipBottom = 0;
+
+ if (mIgnoreTasks.contains(tv.getTask().key)) {
+ // For each of the ignore tasks, update the translationZ of its TaskView to be
+ // between the translationZ of the tasks immediately underneath it
+ if (prevVisibleTv != null) {
+ tv.setTranslationZ(Math.max(tv.getTranslationZ(),
+ prevVisibleTv.getTranslationZ() + 0.1f));
+ }
+ }
+
if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) {
// Find the next view to clip against
for (int j = i + 1; j < taskViewCount; j++) {
@@ -609,33 +748,37 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (!config.useHardwareLayers) {
tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom());
}
+ prevVisibleTv = tv;
}
mTaskViewsClipDirty = false;
}
/**
- * Updates the min and max virtual scroll bounds.
+ * Updates the layout algorithm min and max virtual scroll bounds.
*
- * @param ignoreTasks the set of tasks to ignore in the relayout
+ * @see #updateLayoutAlgorithm(boolean, ArraySet<Task.TaskKey>)
*/
- void updateLayout(boolean boundScrollToNewMinMax, Task... ignoreTasks) {
- // Keep track of the ingore tasks
- ArraySet<Task> ignoreTasksSet = mTmpTaskSet;
- ignoreTasksSet.clear();
- ignoreTasksSet.ensureCapacity(ignoreTasks.length);
- Collections.addAll(ignoreTasksSet, ignoreTasks);
+ void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) {
+ updateLayoutAlgorithm(boundScrollToNewMinMax, mIgnoreTasks);
+ }
+ /**
+ * Updates the min and max virtual scroll bounds.
+ *
+ * @param ignoreTasksSet the set of tasks to ignore in the relayout
+ */
+ void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
+ ArraySet<Task.TaskKey> ignoreTasksSet) {
// Compute the min and max scroll values
mLayoutAlgorithm.update(mStack, ignoreTasksSet);
- // Update the freeform workspace
+ // Update the freeform workspace background
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.hasFreeformWorkspaceSupport()) {
mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
mFreeformWorkspaceBackground.setBounds(mTmpRect);
}
- // Debug logging
if (boundScrollToNewMinMax) {
mStackScroller.boundScroll();
}
@@ -937,10 +1080,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Notify accessibility
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
}
- if (mDeferredTaskViewUpdateAnimation != null) {
- updateTaskViewsToLayout(mDeferredTaskViewUpdateAnimation);
+ if (mDeferredTaskViewLayoutAnimation != null) {
+ relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
mTaskViewsClipDirty = true;
- mDeferredTaskViewUpdateAnimation = null;
+ mDeferredTaskViewLayoutAnimation = null;
}
if (mTaskViewsClipDirty) {
clipTaskViews();
@@ -948,30 +1091,16 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
- * Computes the stack and task rects.
- *
- * @param ignoreTasks the set of tasks to ignore in the relayout
- */
- public void computeRects(Rect taskStackBounds, boolean boundScroll, Task... ignoreTasks) {
- // Compute the rects in the stack algorithm
- mLayoutAlgorithm.initialize(taskStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
-
- // Update the scroll bounds
- updateLayout(boundScroll, ignoreTasks);
- }
-
- /**
* This is ONLY used from the Recents component to update the dummy stack view for purposes
* of getting the task rect to animate to.
*/
public void updateLayoutForStack(TaskStack stack) {
mStack = stack;
- updateLayout(false);
+ updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
}
/**
- * Computes the maximum number of visible tasks and thumbnails. Requires that
+ * Computes the maximum number of visible tasks and thumbnails. Requires that
* updateLayoutForStack() is called first.
*/
public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
@@ -1002,16 +1131,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
- // Compute our stack/task rects
- computeRects(mStackBounds, false);
+ // Compute the rects in the stack algorithm
+ mLayoutAlgorithm.initialize(mStackBounds,
+ TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
// 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();
}
- mTmpTaskSet.clear();
- bindTaskViewsWithStack(mTmpTaskSet);
+ // Rebind all the views, including the ignore ones
+ bindVisibleTaskViews(mStackScroller.getStackScroll(), EMPTY_TASK_SET);
// Measure each of the TaskViews
mTmpTaskViews.clear();
@@ -1066,7 +1197,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mStackScroller.boundScroll();
}
}
- updateTaskViewsToLayout(TaskViewAnimation.IMMEDIATE);
+ // Relayout all of the task views including the ignored ones
+ relayoutTaskViews(TaskViewAnimation.IMMEDIATE, EMPTY_TASK_SET);
clipTaskViews();
if (mAwaitingFirstLayout || !mEnterAnimationComplete) {
@@ -1106,8 +1238,29 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
public boolean isTouchPointInView(float x, float y, TaskView tv) {
- return (tv.getLeft() <= x && x <= tv.getRight()) &&
- (tv.getTop() <= y && y <= tv.getBottom());
+ mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
+ mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY());
+ return mTmpRect.contains((int) x, (int) y);
+ }
+
+ /**
+ * Returns a non-ignored task in the {@param tasks} list that can be used as an achor when
+ * calculating the scroll position before and after a layout change.
+ */
+ public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) {
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ Task task = tasks.get(i);
+
+ // Ignore deleting tasks
+ if (mIgnoreTasks.contains(task.key)) {
+ if (i == tasks.size() - 1) {
+ isFrontMostTask.value = true;
+ }
+ continue;
+ }
+ return task;
+ }
+ return null;
}
@Override
@@ -1152,71 +1305,39 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void onStackTaskAdded(TaskStack stack, Task newTask) {
// Update the min/max scroll and animate other task views into their new positions
- updateLayout(true);
+ updateLayoutAlgorithm(true /* boundScroll */);
// Animate all the tasks into place
- updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
mFastOutSlowInInterpolator));
}
+ /**
+ * We expect that the {@link TaskView} associated with the removed task is already hidden.
+ */
@Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
- Task newFrontMostTask) {
+ Task newFrontMostTask, TaskViewAnimation animation) {
if (mFocusedTask == removedTask) {
resetFocusedTask(removedTask);
}
- if (!removedTask.isFreeformTask()) {
- // 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
- TaskView tv = getChildViewForTask(removedTask);
- if (tv != null) {
- mViewPool.returnViewToPool(tv);
- }
+ // 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
+ TaskView tv = getChildViewForTask(removedTask);
+ if (tv != null) {
+ mViewPool.returnViewToPool(tv);
+ }
- // Get the stack scroll of the task to anchor to (since we are removing something, the
- // front most task will be our anchor task)
- Task anchorTask = mStack.getStackFrontMostTask();
- float prevAnchorTaskScroll = 0;
- if (anchorTask != null) {
- prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
- }
+ // Remove the task from the ignored set
+ removeIgnoreTask(removedTask);
- // Update the min/max scroll and animate other task views into their new positions
- updateLayout(true);
-
- if (wasFrontMostTask) {
- // Since the max scroll progress is offset from the bottom of the stack, just scroll
- // to ensure that the new front most task is now fully visible
- mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
- } else if (anchorTask != null) {
- // Otherwise, offset the scroll by the movement of the anchor task
- float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
- float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
- if (mLayoutAlgorithm.getFocusState() != TaskStackLayoutAlgorithm.STATE_FOCUSED) {
- // If we are focused, we don't want the front task to move, but otherwise, we
- // allow the back task to move up, and the front task to move back
- stackScrollOffset /= 2;
- }
- mStackScroller.setStackScroll(mStackScroller.getStackScroll() + stackScrollOffset);
- mStackScroller.boundScroll();
- }
- } 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
- TaskView tv = getChildViewForTask(removedTask);
- if (tv != null) {
- mViewPool.returnViewToPool(tv);
- }
-
- // Update the min/max scroll and animate other task views into their new positions
- updateLayout(true);
+ // If requested, relayout with the given animation
+ if (animation != null) {
+ updateLayoutAlgorithm(true /* boundScroll */);
+ relayoutTaskViews(animation);
}
- // 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);
@@ -1232,7 +1353,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
@Override
- public void onHistoryTaskRemoved(TaskStack stack, Task removedTask) {
+ public void onHistoryTaskRemoved(TaskStack stack, Task removedTask,
+ TaskViewAnimation animation) {
// To be implemented
}
@@ -1316,7 +1438,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void onTaskViewClipStateChanged(TaskView tv) {
- clipTaskViews();
+ if (!mTaskViewsClipDirty) {
+ mTaskViewsClipDirty = true;
+ invalidate();
+ }
}
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@@ -1324,7 +1449,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) {
mUIDozeTrigger.poke();
- updateTaskViewsToLayoutOnNextFrame(animation);
+ if (animation != null) {
+ relayoutTaskViewsOnNextFrame(animation);
+ }
if (shouldShowHistoryButton() &&
prevScroll > SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD &&
@@ -1354,7 +1481,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
tv.dismissTask();
} else {
// Otherwise, remove the task from the stack immediately
- mStack.removeTask(t);
+ mStack.removeTask(t, TaskViewAnimation.IMMEDIATE);
}
}
}
@@ -1402,7 +1529,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
public final void onBusEvent(TaskViewDismissedEvent event) {
- removeTaskViewFromStack(event.taskView);
+ removeTaskViewFromStack(event.taskView, event.task);
EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
}
@@ -1455,23 +1582,29 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
public final void onBusEvent(DragDropTargetChangedEvent event) {
+ TaskViewAnimation animation = new TaskViewAnimation(250, mFastOutSlowInInterpolator);
if (event.dropTarget instanceof TaskStack.DockState) {
// Calculate the new task stack bounds that matches the window size that Recents will
// have after the drop
+ addIgnoreTask(event.task);
final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
mStackBounds.set(dockState.getDockedTaskStackBounds(getMeasuredWidth(),
getMeasuredHeight(), mDividerSize, mLayoutAlgorithm.mSystemInsets,
getResources()));
- computeRects(mStackBounds, true /* boundScroll */, event.task /* ignoreTask */);
- updateTaskViewsToLayout(new TaskViewAnimation(250, mFastOutSlowInInterpolator),
- event.task /* ignoreTask */);
+ mLayoutAlgorithm.initialize(mStackBounds,
+ TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ updateLayoutAlgorithm(true /* boundScroll */);
} else {
- // Restore the pre-drag task stack bounds
+ // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
+ // task view, so add it back to the ignore set after updating the layout
mStackBounds.set(mStableStackBounds);
- computeRects(mStackBounds, true /* boundScroll */);
- updateTaskViewsToLayout(new TaskViewAnimation(250, mFastOutSlowInInterpolator),
- event.task /* ignoreTask */);
+ removeIgnoreTask(event.task);
+ mLayoutAlgorithm.initialize(mStackBounds,
+ TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ updateLayoutAlgorithm(true /* boundScroll */);
+ addIgnoreTask(event.task);
}
+ relayoutTaskViews(animation);
}
public final void onBusEvent(final DragEndEvent event) {
@@ -1487,14 +1620,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (hasChangedStacks) {
// Move the task to the right position in the stack (ie. the front of the stack if
- // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
+ // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
// before we update their stack ids, otherwise, the keys will have changed.
if (event.dropTarget == mFreeformWorkspaceDropTarget) {
mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
} else if (event.dropTarget == mStackDropTarget) {
mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
}
- updateLayout(true);
+ updateLayoutAlgorithm(true /* boundScroll */);
// Move the task to the new stack in the system after the animation completes
event.addPostAnimationCallback(new Runnable() {
@@ -1522,11 +1655,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
mTmpTransform, null);
event.getAnimationTrigger().increment();
- updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
mFastOutSlowInInterpolator));
updateTaskViewToTransform(event.taskView, mTmpTransform,
new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, mFastOutSlowInInterpolator,
event.getAnimationTrigger().decrementOnAnimationEnd()));
+ removeIgnoreTask(event.task);
}
public final void onBusEvent(StackViewScrolledEvent event) {
@@ -1593,15 +1727,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
* Removes the task from the stack, and updates the focus to the next task in the stack if the
* removed TaskView was focused.
*/
- private void removeTaskViewFromStack(TaskView tv) {
- Task task = tv.getTask();
-
+ private void removeTaskViewFromStack(TaskView tv, Task task) {
// Announce for accessibility
tv.announceForAccessibility(getContext().getString(
- R.string.accessibility_recents_item_dismissed, tv.getTask().title));
+ R.string.accessibility_recents_item_dismissed, task.title));
// Remove the task from the stack
- mStack.removeTask(task);
+ mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ mFastOutSlowInInterpolator));
}
/**
@@ -1622,7 +1755,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
- * Returns the insert index for the task in the current set of task views. If the given task
+ * Returns the insert index for the task in the current set of task views. If the given task
* is already in the task view list, then this method returns the insert index assuming it
* is first removed at the previous index.
*
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 32f02acd9d77..5335b14078dd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -151,6 +151,7 @@ public class TaskStackViewScroller {
/** Animates the stack scroll into bounds */
ObjectAnimator animateBoundScroll() {
+ // TODO: Take duration for snap back
float curScroll = getStackScroll();
float newScroll = getBoundedStackScroll(curScroll);
if (Float.compare(newScroll, curScroll) != 0) {
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 4813c19894e3..e9f6f398a286 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -21,13 +21,16 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.util.Log;
+import android.util.ArrayMap;
+import android.util.MutableBoolean;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
@@ -37,19 +40,25 @@ import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
+import com.android.systemui.recents.misc.RectFEvaluator;
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.statusbar.FlingAnimationUtils;
+import java.util.ArrayList;
import java.util.List;
-/* Handles touch events for a TaskStackView. */
+/**
+ * Handles touch events for a TaskStackView.
+ */
class TaskStackViewTouchHandler implements SwipeHelper.Callback {
- private static final String TAG = "TaskStackViewTouchHandler";
- private static final boolean DEBUG = false;
+ private static final int INACTIVE_POINTER_ID = -1;
- private static int INACTIVE_POINTER_ID = -1;
+ private static final RectFEvaluator RECT_EVALUATOR = new RectFEvaluator();
+ private static final Interpolator STACK_TRANSFORM_INTERPOLATOR =
+ new PathInterpolator(0.73f, 0.33f, 0.42f, 0.85f);
Context mContext;
TaskStackView mSv;
@@ -74,6 +83,15 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
final int mWindowTouchSlop;
private final StackViewScrolledEvent mStackViewScrolledEvent = new StackViewScrolledEvent();
+
+ // The current and final set of task transforms, sized to match the list of tasks in the stack
+ private ArrayList<Task> mCurrentTasks = new ArrayList<>();
+ private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
+ private ArrayList<TaskViewTransform> mFinalTaskTransforms = new ArrayList<>();
+ private ArrayMap<View, Animator> mSwipeHelperAnimations = new ArrayMap<>();
+ private TaskViewTransform mTmpTransform = new TaskViewTransform();
+ private float mTargetStackScroll;
+
SwipeHelper mSwipeHelper;
boolean mInterceptedBySwipeHelper;
@@ -97,8 +115,14 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
}
@Override
- protected void updateSnapBackAnimation(Animator anim) {
+ protected void prepareDismissAnimation(View v, Animator anim) {
+ mSwipeHelperAnimations.put(v, anim);
+ }
+
+ @Override
+ protected void prepareSnapBackAnimation(View v, Animator anim) {
anim.setInterpolator(mSv.mFastOutSlowInInterpolator);
+ mSwipeHelperAnimations.put(v, anim);
}
};
mSwipeHelper.setDisableHardwareLayers(true);
@@ -119,21 +143,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
}
}
- /** Returns the view at the specified coordinates */
- TaskView findViewAtPoint(int x, int y) {
- List<TaskView> taskViews = mSv.getTaskViews();
- int taskViewCount = taskViews.size();
- for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
- if (tv.getVisibility() == View.VISIBLE) {
- if (mSv.isTouchPointInView(x, y, tv)) {
- return tv;
- }
- }
- }
- return null;
- }
-
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Pass through to swipe helper if we are swiping
@@ -179,6 +188,15 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
mScroller.stopBoundScrollAnimation();
Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator);
+ // Finish any existing task animations from the delete
+ mSv.cancelAllTaskViewAnimations();
+ // Finish any of the swipe helper animations
+ ArrayMap<View, Animator> existingAnimators = new ArrayMap<>(mSwipeHelperAnimations);
+ for (int i = 0; i < existingAnimators.size(); i++) {
+ existingAnimators.get(existingAnimators.keyAt(i)).end();
+ }
+ mSwipeHelperAnimations.clear();
+
// Initialize the velocity tracker
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
@@ -214,9 +232,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
float curScrollP = mDownScrollP + deltaP;
mScroller.setStackScroll(curScrollP);
- if (DEBUG) {
- Log.d(TAG, "scroll: " + curScrollP);
- }
mStackViewScrolledEvent.updateY(y - mLastY);
EventBus.getDefault().send(mStackViewScrolledEvent);
}
@@ -343,12 +358,19 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
@Override
public View getChildAtPosition(MotionEvent ev) {
- return findViewAtPoint((int) ev.getX(), (int) ev.getY());
+ TaskView tv = findViewAtPoint((int) ev.getX(), (int) ev.getY());
+ if (tv != null && canChildBeDismissed(tv)) {
+ return tv;
+ }
+ return null;
}
@Override
public boolean canChildBeDismissed(View v) {
- return true;
+ // Disallow dismissing an already dismissed task
+ TaskView tv = (TaskView) v;
+ return !mSwipeHelperAnimations.containsKey(v) &&
+ (mSv.getStack().indexOfStackTask(tv.getTask()) != -1);
}
@Override
@@ -364,34 +386,113 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
+
+ // Add this task to the set of tasks we are deleting
+ mSv.addIgnoreTask(tv.getTask());
+
+ // Determine if we are animating the other tasks while dismissing this task
+ mCurrentTasks = mSv.getStack().getStackTasks();
+ MutableBoolean isFrontMostTask = new MutableBoolean(false);
+ Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask);
+ TaskStackViewScroller stackScroller = mSv.getScroller();
+ if (anchorTask != null) {
+ // Get the current set of task transforms
+ mSv.getCurrentTaskTransforms(mCurrentTasks, mCurrentTaskTransforms);
+
+ // Get the stack scroll of the task to anchor to (since we are removing something, the
+ // front most task will be our anchor task)
+ float prevAnchorTaskScroll = 0;
+ boolean pullStackForward = mCurrentTasks.size() > 0;
+ if (pullStackForward) {
+ prevAnchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
+ }
+
+ // Calculate where the views would be without the deleting tasks
+ mSv.updateLayoutAlgorithm(false /* boundScroll */);
+
+ float newStackScroll = stackScroller.getStackScroll();
+ if (isFrontMostTask.value) {
+ // Bound the stack scroll to pull tasks forward if necessary
+ newStackScroll = stackScroller.getBoundedStackScroll(newStackScroll);
+ } else if (pullStackForward) {
+ // Otherwise, offset the scroll by the movement of the anchor task
+ float anchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
+ float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
+ if (mSv.getStackAlgorithm().getFocusState() !=
+ TaskStackLayoutAlgorithm.STATE_FOCUSED) {
+ // If we are focused, we don't want the front task to move, but otherwise, we
+ // allow the back task to move up, and the front task to move back
+ stackScrollOffset /= 2;
+ }
+ newStackScroll = stackScroller.getBoundedStackScroll(stackScroller.getStackScroll()
+ + stackScrollOffset);
+ }
+
+ // Pick up the newly visible views, not including the deleting tasks
+ mSv.bindVisibleTaskViews(newStackScroll);
+
+ // Get the final set of task transforms (with task removed)
+ mSv.getLayoutTaskTransforms(newStackScroll, mCurrentTasks, mFinalTaskTransforms);
+
+ // Set the target to scroll towards upon dismissal
+ mTargetStackScroll = newStackScroll;
+
+ /*
+ * Post condition: All views that will be visible as a part of the gesture are retrieved
+ * and at their initial positions. The stack is still at the current
+ * scroll, but the layout is updated without the task currently being
+ * dismissed.
+ */
+ }
}
@Override
public boolean updateSwipeProgress(View v, boolean dismissable, float swipeProgress) {
+ updateTaskViewTransforms(getDismissFraction(v));
return true;
}
+ /**
+ * Called after the {@link TaskView} is finished animating away.
+ */
@Override
public void onChildDismissed(View v) {
TaskView tv = (TaskView) v;
+
// Re-enable clipping with the stack (we will reuse this view)
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
+ // Update the scroll to the final scroll position from onBeginDrag()
+ mSv.getScroller().setStackScroll(mTargetStackScroll, null);
// Remove the task view from the stack
EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv));
+ // Stop tracking this deletion animation
+ mSwipeHelperAnimations.remove(v);
// Keep track of deletions by keyboard
MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source",
Constants.Metrics.DismissSourceSwipeGesture);
}
+ /**
+ * Called after the {@link TaskView} is finished animating back into the list.
+ * onChildDismissed() calls.
+ */
@Override
public void onChildSnappedBack(View v) {
TaskView tv = (TaskView) v;
+
// Re-enable clipping with the stack
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
+
+ // Stop tracking this deleting task, and update the layout to include this task again. The
+ // stack scroll does not need to be reset, since the scroll has not actually changed in
+ // onBeginDrag().
+ mSv.removeIgnoreTask(tv.getTask());
+ mSv.updateLayoutAlgorithm(false /* boundScroll */);
+ mSwipeHelperAnimations.remove(v);
}
@Override
@@ -414,4 +515,59 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
return 0;
}
+ /**
+ * Interpolates the non-deleting tasks to their final transforms from their current transforms.
+ */
+ private void updateTaskViewTransforms(float dismissFraction) {
+ List<TaskView> taskViews = mSv.getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
+ Task task = tv.getTask();
+
+ if (mSv.isIgnoredTask(task)) {
+ continue;
+ }
+
+ int taskIndex = mCurrentTasks.indexOf(task);
+ TaskViewTransform fromTransform = mCurrentTaskTransforms.get(taskIndex);
+ TaskViewTransform toTransform = mFinalTaskTransforms.get(taskIndex);
+
+ mTmpTransform.copyFrom(fromTransform);
+ // We only really need to interpolate the bounds, progress and translation
+ mTmpTransform.rect.set(RECT_EVALUATOR.evaluate(dismissFraction, fromTransform.rect,
+ toTransform.rect));
+ mTmpTransform.p = fromTransform.p + (toTransform.p - fromTransform.p) * dismissFraction;
+ mTmpTransform.translationZ = fromTransform.translationZ +
+ (toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
+
+ mSv.updateTaskViewToTransform(tv, mTmpTransform, TaskViewAnimation.IMMEDIATE);
+ }
+ }
+
+ /** Returns the view at the specified coordinates */
+ private TaskView findViewAtPoint(int x, int y) {
+ List<Task> tasks = mSv.getStack().getStackTasks();
+ int taskCount = tasks.size();
+ for (int i = taskCount - 1; i >= 0; i--) {
+ TaskView tv = mSv.getChildViewForTask(tasks.get(i));
+ if (tv != null && tv.getVisibility() == View.VISIBLE) {
+ if (mSv.isTouchPointInView(x, y, tv)) {
+ return tv;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the fraction which we should interpolate the other task views based on the dismissal
+ * of this given task.
+ *
+ * TODO: We can interpolate this to adjust when the other tasks should respond to the dismissal
+ */
+ private float getDismissFraction(View v) {
+ float fraction = Math.min(1f, Math.abs(v.getTranslationX() / mSv.getWidth()));
+ return STACK_TRANSFORM_INTERPOLATOR.getInterpolation(fraction);
+ }
}
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 538c2483cb9f..85b7c82112a4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -88,12 +88,39 @@ public class TaskViewTransform {
public float alpha = 1f;
public boolean visible = false;
- float p = 0f;
+
+ // This is the relative task progress of this task, relative to the stack scroll at which this
+ // transform was computed
+ public float p = 0f;
// This is a window-space rect used for positioning the task in the stack and freeform workspace
public RectF rect = new RectF();
/**
+ * Fills int this transform from the state of the given TaskView.
+ */
+ public void fillIn(TaskView tv) {
+ translationZ = tv.getTranslationZ();
+ scale = tv.getScaleX();
+ alpha = tv.getAlpha();
+ visible = true;
+ p = tv.getTaskProgress();
+ rect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
+ }
+
+ /**
+ * Copies the transform state from another {@link TaskViewTransform}.
+ */
+ public void copyFrom(TaskViewTransform other) {
+ translationZ = other.translationZ;
+ scale = other.scale;
+ alpha = other.alpha;
+ visible = other.visible;
+ p = other.p;
+ rect.set(other.rect);
+ }
+
+ /**
* Resets the current transform.
*/
public void reset() {