Initial layout/behaviour for freeform workspace.

Change-Id: Idc878862747d0d726cbf10be9893c48736673d0e
This commit is contained in:
Winson
2015-10-29 18:04:39 -07:00
parent 53ec42cb17
commit 36a5a2c700
11 changed files with 500 additions and 218 deletions

View File

@@ -616,15 +616,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
ctx.postAnimationTrigger.increment();
if (mSearchWidgetInfo != null) {
ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
// Start listening for widget package changes if there is one bound
if (!Constants.DebugFlags.App.DisableSearchBar && mAppWidgetHost != null) {
mAppWidgetHost.startListening();
if (!Constants.DebugFlags.App.DisableSearchBar) {
ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
// Start listening for widget package changes if there is one bound
if (mAppWidgetHost != null) {
mAppWidgetHost.startListening();
}
}
}
});
});
}
}
ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override

View File

@@ -53,7 +53,7 @@ import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskGrouping;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.TaskStackView;
import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -465,10 +465,10 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
mSearchBarBounds, mTaskStackBounds);
// Rebind the header bar and draw it for the transition
TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
TaskStackLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
Rect taskStackBounds = new Rect(mTaskStackBounds);
algo.setSystemInsets(systemInsets);
algo.computeRects(taskStackBounds);
algo.initialize(taskStackBounds);
Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
if (!taskViewBounds.equals(mLastTaskViewBounds)) {
mLastTaskViewBounds.set(taskViewBounds);
@@ -666,7 +666,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
// Prepare the dummy stack for the transition
mDummyStackView.updateMinMaxScrollForStack(stack);
TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
TaskStackLayoutAlgorithm.VisibilityReport stackVr =
mDummyStackView.computeStackVisibilityReport();
boolean hasRecentTasks = stack.getTaskCount() > 0;
boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
@@ -713,7 +713,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
*/
private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
TaskStackLayoutAlgorithm.VisibilityReport vr) {
mStartAnimationTriggered = false;
// Update the configuration based on the launch options

View File

@@ -137,12 +137,19 @@ public class RecentsTaskLoadPlan {
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
stackTasks.add(task);
if (task.isFreeformTask()) {
freeformTasks.add(task);
} else {
stackTasks.add(task);
}
}
// Initialize the stacks
ArrayList<Task> allTasks = new ArrayList<>();
allTasks.addAll(stackTasks);
allTasks.addAll(freeformTasks);
mStack = new TaskStack();
mStack.setTasks(stackTasks);
mStack.setTasks(allTasks);
mStack.createAffiliatedGroupings(mContext);
}

View File

@@ -399,6 +399,21 @@ public class TaskStack {
return mTaskList.size();
}
/**
* Returns the task in this stack which is the launch target.
*/
public Task getLaunchTarget() {
ArrayList<Task> tasks = mTaskList.getTasks();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
Task task = tasks.get(i);
if (task.isLaunchTarget) {
return task;
}
}
return null;
}
/** Returns the index of this task in this current task stack */
public int indexOfTask(Task t) {
return mTaskList.indexOf(t);
@@ -417,6 +432,21 @@ public class TaskStack {
return null;
}
/**
* Returns whether this stack has freeform tasks.
*/
public boolean hasFreeformTasks() {
ArrayList<Task> tasks = mTaskList.getTasks();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
Task task = tasks.get(i);
if (task.isFreeformTask()) {
return true;
}
}
return false;
}
/******** Filtering ********/
/** Filters the stack into tasks similar to the one specified */

View File

@@ -0,0 +1,108 @@
/*
* 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.util.Log;
import com.android.systemui.recents.model.Task;
import java.util.ArrayList;
import java.util.HashMap;
/**
* The layout logic for the contents of the freeform workspace.
*/
public class FreeformWorkspaceLayoutAlgorithm {
private static final String TAG = "FreeformWorkspaceLayoutAlgorithm";
private static final boolean DEBUG = false;
// The number of cells in the freeform workspace
private int mFreeformCellXCount;
private int mFreeformCellYCount;
// The width and height of the cells in the freeform workspace
private int mFreeformCellWidth;
private int mFreeformCellHeight;
// Optimization, allows for quick lookup of task -> index
private HashMap<Task.TaskKey, Integer> mTaskIndexMap = new HashMap<>();
/**
* Updates the layout for each of the freeform workspace tasks. This is called after the stack
* layout is updated.
*/
public void update(ArrayList<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
int numFreeformTasks = stackLayout.mNumFreeformTasks;
if (!freeformTasks.isEmpty()) {
// Calculate the cell width/height depending on the number of freeform tasks
mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks / mFreeformCellXCount));
mFreeformCellWidth = stackLayout.mFreeformRect.width() / mFreeformCellXCount;
// For now, make the cells square
mFreeformCellHeight = mFreeformCellWidth;
// Put each of the tasks in the progress map at a fixed index (does not need to actually
// map to a scroll position, just by index)
int taskCount = freeformTasks.size();
for (int i = 0; i < taskCount; i++) {
Task task = freeformTasks.get(i);
mTaskIndexMap.put(task.key, i);
}
if (DEBUG) {
Log.d(TAG, "mFreeformCellXCount: " + mFreeformCellXCount);
Log.d(TAG, "mFreeformCellYCount: " + mFreeformCellYCount);
Log.d(TAG, "mFreeformCellWidth: " + mFreeformCellWidth);
Log.d(TAG, "mFreeformCellHeight: " + mFreeformCellHeight);
}
}
}
/**
* Returns whether the transform is available for the given task.
*/
public boolean isTransformAvailable(Task task, float stackScroll,
TaskStackLayoutAlgorithm stackLayout) {
if (stackLayout.mNumFreeformTasks == 0 || task == null ||
!mTaskIndexMap.containsKey(task.key)) {
return false;
}
return stackScroll > stackLayout.mStackEndScrollP;
}
/**
* Returns the transform for the given task. Any rect returned will be offset by the actual
* transform for the freeform workspace.
*/
public TaskViewTransform getTransform(Task task, float stackScroll,
TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
if (Float.compare(stackScroll, stackLayout.mStackEndScrollP) > 0) {
// This is a freeform task, so lay it out in the freeform workspace
int taskIndex = mTaskIndexMap.get(task.key);
int x = taskIndex % mFreeformCellXCount;
int y = taskIndex / mFreeformCellXCount;
float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width();
int scaleXOffset = (int) (((1f - scale) * stackLayout.mTaskRect.width()) / 2);
int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2);
transformOut.scale = scale * 0.9f;
transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
transformOut.translationY = y * mFreeformCellHeight - scaleYOffset;
transformOut.visible = true;
return transformOut;
}
return null;
}
}

View File

@@ -26,6 +26,7 @@ import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.ParametricCurve;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
import java.util.HashMap;
@@ -33,9 +34,8 @@ import java.util.HashMap;
/**
* The layout logic for a TaskStackView.
*
*/
public class TaskStackViewLayoutAlgorithm {
public class TaskStackLayoutAlgorithm {
private static final String TAG = "TaskStackViewLayoutAlgorithm";
private static final boolean DEBUG = false;
@@ -46,6 +46,10 @@ public class TaskStackViewLayoutAlgorithm {
private static final float SINGLE_TASK_SCALE = 0.95f;
// The percentage of height of task to show between tasks
private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
// The percentage between the maxStackScroll and the maxScroll where a given scroll will still
// snap back to the maxStackScroll instead of to the maxScroll (which shows the freeform
// workspace)
private static final float SNAP_TO_MAX_STACK_SCROLL_FACTOR = 0.3f;
// A report of the visibility state of the stack
public class VisibilityReport {
@@ -64,11 +68,9 @@ public class TaskStackViewLayoutAlgorithm {
// This is the view bounds inset exactly by the search bar, but without the bottom inset
// see RecentsConfiguration.getTaskStackBounds()
public Rect mStackRect = new Rect();
// This is the task view bounds for layout (untransformed), the rect is top-aligned to the top
// of the stack rect
public Rect mTaskRect = new Rect();
// The bounds of the freeform workspace, the rect is top-aligned to the top of the stack rect
public Rect mFreeformRect = new Rect();
// This is the current system insets
@@ -79,10 +81,14 @@ public class TaskStackViewLayoutAlgorithm {
// The largest scroll progress, at this value, the front most task will be visible above the
// navigation bar
float mMaxScrollP;
// The scroll progress at which the stack scroll ends and the overscroll begins. This serves
// as the point at which we can show the freeform space.
float mMaxStackScrollP;
// The initial progress that the scroller is set
// The scroll progress at which bottom of the first task of the stack is aligned with the bottom
// of the stack
float mStackEndScrollP;
// The scroll progress that we actually want to scroll the user to when they want to go to the
// end of the stack (it accounts for the nav bar, so that the bottom of the task is offset from
// the bottom of the stack)
float mPreferredStackEndScrollP;
// The initial progress that the scroller is set when you first enter recents
float mInitialScrollP;
// The task progress for the front-most task in the stack
float mFrontMostTaskP;
@@ -96,20 +102,18 @@ public class TaskStackViewLayoutAlgorithm {
float mTaskHeightPOffset;
// The relative progress to ensure that the half task height is respected
float mTaskHalfHeightPOffset;
// The front-most task bottom offset
int mStackBottomOffset;
// The relative progress to ensure that the offset from the bottom of the stack to the bottom
// of the task is respected
float mTaskBottomPOffset;
// The relative progress to ensure that the freeform workspace height is respected
float mStackBottomPOffset;
// The freeform workspace gap
int mFreeformWorkspaceGapOffset;
float mFreeformWorkspaceGapPOffset;
// The relative progress to ensure that the freeform workspace height + gap + stack bottom
// padding is respected
int mFreeformWorkspaceOffset;
float mFreeformWorkspacePOffset;
// The front-most task bottom offset
int mTaskBottomOffset;
// The number of cells in the freeform workspace
int mFreeformCellXCount;
int mFreeformCellYCount;
// The width and height of the cells in the freeform workspace
int mFreeformCellWidth;
int mFreeformCellHeight;
// The last computed task counts
int mNumStackTasks;
@@ -121,14 +125,21 @@ public class TaskStackViewLayoutAlgorithm {
// Optimization, allows for quick lookup of task -> progress
HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<>();
// The freeform workspace layout
FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
// Temporary task view transform
TaskViewTransform mTmpTransform = new TaskViewTransform();
// Log function
static ParametricCurve sCurve;
public TaskStackViewLayoutAlgorithm(Context context) {
public TaskStackLayoutAlgorithm(Context context) {
Resources res = context.getResources();
mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
mContext = context;
mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm();
if (sCurve == null) {
sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() {
// The large the XScale, the longer the flat area of the curve
@@ -151,6 +162,11 @@ public class TaskStackViewLayoutAlgorithm {
}, new ParametricCurve.ParametricCurveFunction() {
@Override
public float f(float p) {
// Don't scale when there are freeform tasks
if (mNumFreeformTasks > 0) {
return 1f;
}
if (p < 0) return STACK_PEEK_MIN_SCALE;
if (p > 1) return 1f;
float scaleRange = (1f - STACK_PEEK_MIN_SCALE);
@@ -174,7 +190,7 @@ public class TaskStackViewLayoutAlgorithm {
/**
* Computes the stack and task rects.
*/
public void computeRects(Rect taskStackBounds) {
public void initialize(Rect taskStackBounds) {
RecentsConfiguration config = Recents.getConfiguration();
int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
int heightPadding = mContext.getResources().getDimensionPixelSize(
@@ -183,14 +199,17 @@ public class TaskStackViewLayoutAlgorithm {
// Compute the stack rect, inset from the given task stack bounds
mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding,
taskStackBounds.right - widthPadding, taskStackBounds.bottom);
mTaskBottomOffset = mSystemInsets.bottom + heightPadding;
mStackBottomOffset = mSystemInsets.bottom + heightPadding;
// Compute the task rect, align it to the top-center square in the stack rect
int size = Math.min(mStackRect.width(), mStackRect.height() - mTaskBottomOffset);
int size = Math.min(mStackRect.width(), mStackRect.height() - mStackBottomOffset);
int xOffset = (mStackRect.width() - size) / 2;
mTaskRect.set(mStackRect.left + xOffset, mStackRect.top,
mStackRect.right - xOffset, mStackRect.top + size);
mFreeformRect.set(mTaskRect);
// Compute the freeform rect, align it to the top-left of the stack rect
mFreeformRect.set(mStackRect);
mFreeformRect.bottom = taskStackBounds.bottom - mStackBottomOffset;
// Compute the progress offsets
int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
@@ -204,12 +223,17 @@ public class TaskStackViewLayoutAlgorithm {
mStackRect);
mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
mStackRect);
mTaskBottomPOffset = sCurve.computePOffsetForHeight(mTaskBottomOffset, mStackRect);
mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformRect.height(),
mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mStackRect);
mFreeformWorkspaceGapOffset = mStackBottomOffset;
mFreeformWorkspaceGapPOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceGapOffset,
mStackRect);
mFreeformWorkspaceOffset = mFreeformWorkspaceGapOffset + mFreeformRect.height() +
mStackBottomOffset;
mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceOffset,
mStackRect);
if (DEBUG) {
Log.d(TAG, "computeRects");
Log.d(TAG, "initialize");
Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
Log.d(TAG, "\tmStackRect: " + mStackRect);
Log.d(TAG, "\tmTaskRect: " + mTaskRect);
@@ -219,12 +243,14 @@ public class TaskStackViewLayoutAlgorithm {
Log.d(TAG, "\tpBetweenAffiliateOffset: " + mBetweenAffiliationPOffset);
Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
Log.d(TAG, "\tmTaskBottomPOffset: " + mTaskBottomPOffset);
Log.d(TAG, "\tmStackBottomPOffset: " + mStackBottomPOffset);
Log.d(TAG, "\tmFreeformWorkspacePOffset: " + mFreeformWorkspacePOffset);
Log.d(TAG, "\tmFreeformWorkspaceGapPOffset: " + mFreeformWorkspaceGapPOffset);
Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mStackRect));
Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mStackRect));
for (int height = 0; height <= 1000; height += 50) {
for (int height = 0; height <= 2000; height += 50) {
float p = sCurve.computePOffsetForScaledHeight(height, mStackRect);
float p2 = sCurve.computePOffsetForHeight(height, mStackRect);
Log.d(TAG, "offset: " + height + ", " +
@@ -236,20 +262,22 @@ public class TaskStackViewLayoutAlgorithm {
}
/**
* Computes the minimum and maximum scroll progress values. This method may be called before
* the RecentsConfiguration is set, so we need to pass in the alt-tab state.
* Computes the minimum and maximum scroll progress values and the progress values for each task
* in the stack.
*/
void computeMinMaxScroll(ArrayList<Task> tasks) {
void update(TaskStack stack) {
if (DEBUG) {
Log.d(TAG, "computeMinMaxScroll");
Log.d(TAG, "update");
}
// Clear the progress map
mTaskProgressMap.clear();
// Return early if we have no tasks
ArrayList<Task> tasks = stack.getTasks();
if (tasks.isEmpty()) {
mMinScrollP = mMaxScrollP = mMaxStackScrollP = 0;
mFrontMostTaskP = 0;
mMinScrollP = mMaxScrollP = mStackEndScrollP = mPreferredStackEndScrollP = 0;
mNumStackTasks = mNumFreeformTasks = 0;
return;
}
@@ -268,9 +296,6 @@ public class TaskStackViewLayoutAlgorithm {
mNumStackTasks = stackTasks.size();
mNumFreeformTasks = freeformTasks.size();
// TODO: In the case where there is only freeform tasks, then the scrolls should be set to
// zero
if (!stackTasks.isEmpty()) {
// Update the for each task from back to front.
float pAtBackMostTaskTop = 0;
@@ -289,52 +314,47 @@ public class TaskStackViewLayoutAlgorithm {
}
mFrontMostTaskP = pAtFrontMostTaskTop;
// Set the max scroll progress to the point at which the top of the front-most task
// is aligned to the bottom of the stack (offset by nav bar, padding, and task height)
mMaxStackScrollP = getBottomAlignedScrollProgress(pAtFrontMostTaskTop,
mTaskBottomPOffset + mTaskHeightPOffset);
// Set the stack end scroll progress to the point at which the bottom of the front-most
// task is aligned to the bottom of the stack
mStackEndScrollP = alignToStackBottom(pAtFrontMostTaskTop,
mTaskHeightPOffset);
// Set the preferred stack end scroll progress to the point where the bottom of the
// front-most task is offset by the navbar and padding from the bottom of the stack
mPreferredStackEndScrollP = mStackEndScrollP + mStackBottomPOffset;
// Basically align the back-most task such that its progress is the same as the top of
// the front most task at the max stack scroll
mMinScrollP = getBottomAlignedScrollProgress(pAtBackMostTaskTop,
mTaskBottomPOffset + mTaskHeightPOffset);
mMinScrollP = alignToStackBottom(pAtBackMostTaskTop,
mStackBottomPOffset + mTaskHeightPOffset);
} else {
// TODO: In the case where there is only freeform tasks, then the scrolls should be
// set to zero
}
if (!freeformTasks.isEmpty()) {
// Calculate the cell width/height depending on the number of freeform tasks
mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(mNumFreeformTasks)));
mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) mNumFreeformTasks / mFreeformCellXCount));
mFreeformCellWidth = mFreeformRect.width() / mFreeformCellXCount;
mFreeformCellHeight = mFreeformRect.height() / mFreeformCellYCount;
// Put each of the tasks in the progress map at a fixed index (does not need to actually
// map to a scroll position, just by index)
int taskCount = freeformTasks.size();
for (int i = 0; i < taskCount; i++) {
Task task = freeformTasks.get(i);
mTaskProgressMap.put(task.key, (mFrontMostTaskP + 1) + i);
}
// The max scroll includes the freeform workspace offset. As the scroll progress exceeds
// mMaxStackScrollP up to mMaxScrollP, the stack will translate upwards and the freeform
// mStackEndScrollP up to mMaxScrollP, the stack will translate upwards and the freeform
// workspace will be visible
mMaxScrollP = mMaxStackScrollP + mFreeformWorkspacePOffset;
mInitialScrollP = mMaxScrollP;
mFreeformLayoutAlgorithm.update(freeformTasks, this);
mMaxScrollP = mStackEndScrollP + mFreeformWorkspacePOffset;
mInitialScrollP = isInitialStateFreeform(stack) ?
mMaxScrollP : mPreferredStackEndScrollP;
} else {
mMaxScrollP = mMaxStackScrollP;
mInitialScrollP = Math.max(mMinScrollP, mMaxStackScrollP - mTaskHalfHeightPOffset);
mMaxScrollP = mPreferredStackEndScrollP;
mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
}
if (DEBUG) {
Log.d(TAG, "mNumStackTasks: " + mNumStackTasks);
Log.d(TAG, "mNumFreeformTasks: " + mNumFreeformTasks);
Log.d(TAG, "mMinScrollP: " + mMinScrollP);
Log.d(TAG, "mMaxStackScrollP: " + mMaxStackScrollP);
Log.d(TAG, "mStackEndScrollP: " + mStackEndScrollP);
Log.d(TAG, "mMaxScrollP: " + mMaxScrollP);
}
}
/**
* Computes the maximum number of visible tasks and thumbnails. Requires that
* computeMinMaxScroll() is called first.
* update() is called first.
*/
public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
// Ensure minimum visibility count
@@ -380,7 +400,7 @@ public class TaskStackViewLayoutAlgorithm {
prevScreenY = screenY;
} else {
// Once we hit the next front most task that does not have a visible thumbnail,
// walk through remaining visible set
// w alk through remaining visible set
for (int j = i; j >= 0; j--) {
numVisibleTasks++;
progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP;
@@ -404,50 +424,42 @@ public class TaskStackViewLayoutAlgorithm {
*/
public TaskViewTransform getStackTransform(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform prevTransform) {
// Return early if we have an invalid index
if (task == null || !mTaskProgressMap.containsKey(task.key)) {
transformOut.reset();
if (mFreeformLayoutAlgorithm.isTransformAvailable(task, stackScroll, this)) {
mFreeformLayoutAlgorithm.getTransform(task, stackScroll, transformOut, this);
if (transformOut.visible) {
getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
transformOut.translationY += mTmpTransform.translationY;
transformOut.translationZ = mMaxTranslationZ;
transformOut.rect.set(mTaskRect);
transformOut.rect.offset(0, transformOut.translationY);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.p = 0;
}
return transformOut;
} else {
// Return early if we have an invalid index
if (task == null || !mTaskProgressMap.containsKey(task.key)) {
transformOut.reset();
return transformOut;
}
return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
prevTransform);
}
return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
prevTransform);
}
/** Update/get the transform */
public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform prevTransform) {
float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset;
int overscrollYOffset = 0;
if (mNumFreeformTasks > 0) {
overscrollYOffset = (int) (Math.max(0, stackOverscroll) * mFreeformRect.height());
}
if ((mNumFreeformTasks > 0) && (stackScroll > mMaxStackScrollP) &&
(taskProgress > mFrontMostTaskP)) {
// This is a freeform task, so lay it out in the freeform workspace
int taskIndex = Math.round(taskProgress - (mFrontMostTaskP + 1));
int x = taskIndex % mFreeformCellXCount;
int y = taskIndex / mFreeformCellXCount;
int frontTaskBottom = mStackRect.height() - mTaskBottomOffset;
float scale = (float) mFreeformCellWidth / mTaskRect.width();
int scaleXOffset = (int) (((1f - scale) * mTaskRect.width()) / 2);
int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
transformOut.scale = scale;
transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
transformOut.translationY = frontTaskBottom - overscrollYOffset +
(y * mFreeformCellHeight) - scaleYOffset;
transformOut.visible = true;
return transformOut;
} else if (mNumStackTasks == 1) {
if (mNumStackTasks == 1) {
// Center the task in the stack, changing the scale will not follow the curve, but just
// modulate some values directly
float pTaskRelative = mMinScrollP - stackScroll;
float scale = SINGLE_TASK_SCALE;
int topOffset = (mStackRect.height() - mTaskBottomOffset - mTaskRect.height()) / 2;
int topOffset = (mStackRect.height() - mTaskRect.height()) / 2;
transformOut.scale = scale;
transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height())) -
overscrollYOffset;
transformOut.translationX = 0;
transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height()));
transformOut.translationZ = mMaxTranslationZ;
transformOut.rect.set(mTaskRect);
transformOut.rect.offset(0, transformOut.translationY);
@@ -457,11 +469,20 @@ public class TaskStackViewLayoutAlgorithm {
return transformOut;
} else {
float pTaskRelative = taskProgress - stackScroll;
if (mNumFreeformTasks > 0) {
pTaskRelative = Math.min(mMaxStackScrollP, pTaskRelative);
// Once we scroll past the preferred stack end scroll, then we should start translating
// the cards in screen space and lock their final state at the end stack progress
int overscrollYOffset = 0;
if (mNumFreeformTasks > 0 && stackScroll > mStackEndScrollP) {
float stackOverscroll = (stackScroll - mPreferredStackEndScrollP) /
(mFreeformWorkspacePOffset - mFreeformWorkspaceGapPOffset);
overscrollYOffset = (int) (Math.max(0, stackOverscroll) *
(mFreeformWorkspaceOffset - mFreeformWorkspaceGapPOffset));
stackScroll = Math.min(mPreferredStackEndScrollP, stackScroll);
}
float pTaskRelative = taskProgress - stackScroll;
float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
// If the task top is outside of the bounds below the screen, then immediately reset it
if (pTaskRelative > 1f) {
transformOut.reset();
@@ -480,6 +501,7 @@ public class TaskStackViewLayoutAlgorithm {
float scale = sCurve.pToScale(pBounded);
int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
transformOut.scale = scale;
transformOut.translationX = 0;
transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top -
scaleYOffset - overscrollYOffset;
transformOut.translationZ = Math.max(mMinTranslationZ,
@@ -489,10 +511,30 @@ public class TaskStackViewLayoutAlgorithm {
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
transformOut.p = pTaskRelative;
if (DEBUG) {
Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
Log.d(TAG, "\t" + transformOut);
}
return transformOut;
}
}
/**
* Returns whether this stack should be initialized to show the freeform workspace or not.
*/
public boolean isInitialStateFreeform(TaskStack stack) {
Task launchTarget = stack.getLaunchTarget();
if (launchTarget != null) {
return launchTarget.isFreeformTask();
}
Task frontTask = stack.getFrontMostTask();
if (frontTask != null) {
return frontTask.isFreeformTask();
}
return false;
}
/**
* Update/get the transform
*/
@@ -503,17 +545,36 @@ public class TaskStackViewLayoutAlgorithm {
return transformOut;
}
if (stackScroll > mMaxStackScrollP) {
float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset;
int overscrollYOffset = (int) (stackOverscroll * mFreeformRect.height());
int frontTaskBottom = mStackRect.height() - mTaskBottomOffset;
if (stackScroll > mStackEndScrollP) {
// mStackEndScroll is the point at which the first stack task is bottom aligned with the
// stack, so we offset from on the stack rect height.
float stackOverscroll = (Math.max(0, stackScroll - mStackEndScrollP)) /
mFreeformWorkspacePOffset;
int overscrollYOffset = (int) (stackOverscroll * mFreeformWorkspaceOffset);
transformOut.scale = 1f;
transformOut.alpha = 1f;
transformOut.translationY = mStackRect.height() + mFreeformWorkspaceGapOffset -
overscrollYOffset;
transformOut.rect.set(mFreeformRect);
transformOut.rect.offset(0, transformOut.translationY);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
transformOut.alpha =
transformOut.translationY = frontTaskBottom - overscrollYOffset;
}
return transformOut;
}
/**
* Returns the preferred maximum scroll position for a stack at the given {@param scroll}.
*/
public float getPreferredMaxScrollPosition(float scroll) {
float maxStackScrollBounds = mStackEndScrollP + SNAP_TO_MAX_STACK_SCROLL_FACTOR *
(mMaxScrollP - mStackEndScrollP);
if (scroll < maxStackScrollBounds) {
return mPreferredStackEndScrollP;
}
return mMaxScrollP;
}
/**
* Returns the untransformed task view bounds.
*/
@@ -549,7 +610,12 @@ public class TaskStackViewLayoutAlgorithm {
return -y;
}
private float getBottomAlignedScrollProgress(float p, float pOffsetFromBottom) {
private float alignToStackTop(float p) {
// At scroll progress == p, then p is at the top of the stack
return p;
}
private float alignToStackBottom(float p, float pOffsetFromBottom) {
// At scroll progress == p, then p is at the top of the stack
// At scroll progress == p + 1, then p is at the bottom of the stack
return p - (1 - pOffsetFromBottom);

View File

@@ -22,12 +22,12 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
@@ -79,11 +79,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
TaskStack mStack;
TaskStackViewLayoutAlgorithm mLayoutAlgorithm;
TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewFilterAlgorithm mFilterAlgorithm;
TaskStackViewScroller mStackScroller;
TaskStackViewTouchHandler mTouchHandler;
TaskStackViewCallbacks mCb;
ColorDrawable mFreeformWorkspaceBackground;
ViewPool<TaskView, Task> mViewPool;
ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
DozeTrigger mUIDozeTrigger;
@@ -101,6 +102,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Rect mTmpRect = new Rect();
RectF mTmpTaskRect = new RectF();
TaskViewTransform mTmpTransform = new TaskViewTransform();
TaskViewTransform mTmpStackBackTransform = new TaskViewTransform();
TaskViewTransform mTmpStackFrontTransform = new TaskViewTransform();
HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
ArrayList<TaskView> mTaskViews = new ArrayList<>();
List<TaskView> mImmutableTaskViews = new ArrayList<>();
@@ -122,7 +125,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
setStack(stack);
mViewPool = new ViewPool<>(context, this);
mInflater = LayoutInflater.from(context);
mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context);
mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context);
mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool);
mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm);
mStackScroller.setCallbacks(this);
@@ -143,6 +146,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
});
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
mFreeformWorkspaceBackground = new ColorDrawable(0x33000000);
}
/** Sets the callbacks */
@@ -270,7 +275,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/** Returns the stack algorithm for this task stack. */
public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
public TaskStackLayoutAlgorithm getStackAlgorithm() {
return mLayoutAlgorithm;
}
@@ -342,6 +347,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int[] visibleRange = mTmpVisibleRange;
boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
stackScroll, visibleRange, false);
boolean hasStackBackTransform = false;
boolean hasStackFrontTransform = false;
if (DEBUG) {
Log.d(TAG, "visibleRange: " + visibleRange[0] + " to " + visibleRange[1]);
}
// Return all the invisible children to the pool
mTmpTaskViewMap.clear();
@@ -381,11 +391,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// 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) {
mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null);
if (!hasStackBackTransform) {
hasStackBackTransform = true;
mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpStackBackTransform,
null);
}
tv.updateViewPropertiesToTaskTransform(mTmpStackBackTransform, 0);
} else {
mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null);
if (!hasStackFrontTransform) {
hasStackFrontTransform = true;
mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpStackFrontTransform,
null);
}
tv.updateViewPropertiesToTaskTransform(mTmpStackFrontTransform, 0);
}
tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
}
}
@@ -403,6 +422,16 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
// Update the freeform workspace
mLayoutAlgorithm.getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
if (mTmpTransform.visible) {
mTmpTransform.rect.roundOut(mTmpRect);
mFreeformWorkspaceBackground.setAlpha(255);
mFreeformWorkspaceBackground.setBounds(mTmpRect);
} else {
mFreeformWorkspaceBackground.setAlpha(0);
}
// Reset the request-synchronize params
mStackViewsAnimationDuration = 0;
mStackViewsDirty = false;
@@ -460,7 +489,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** Updates the min and max virtual scroll bounds */
void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
// Compute the min and max scroll values
mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks());
mLayoutAlgorithm.update(mStack);
// Debug logging
if (boundScrollToNewMinMax) {
@@ -512,7 +541,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
};
if (scrollToTask) {
// TODO: Center the newly focused task view
// TODO: Center the newly focused task view, only if not freeform
float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f;
newScroll = mStackScroller.getBoundedStackScroll(newScroll);
mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
@@ -633,7 +662,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** Computes the stack and task rects */
public void computeRects(Rect taskStackBounds) {
// Compute the rects in the stack algorithm
mLayoutAlgorithm.computeRects(taskStackBounds);
mLayoutAlgorithm.initialize(taskStackBounds);
// Update the scroll bounds
updateMinMaxScroll(false);
@@ -652,7 +681,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
* Computes the maximum number of visible tasks and thumbnails. Requires that
* updateMinMaxScrollForStack() is called first.
*/
public TaskStackViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
}
@@ -710,7 +739,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// Layout each of the children
// Layout each of the TaskViews
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
@@ -720,10 +749,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
} else {
mTmpRect.setEmpty();
}
tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left,
mLayoutAlgorithm.mTaskRect.top - mTmpRect.top,
mLayoutAlgorithm.mTaskRect.right + mTmpRect.right,
mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
Rect taskRect = mLayoutAlgorithm.mTaskRect;
tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
}
if (mAwaitingFirstLayout) {
@@ -737,17 +765,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int offscreenY = mLayoutAlgorithm.mStackRect.bottom;
// Find the launch target task
Task launchTargetTask = null;
Task launchTargetTask = mStack.getLaunchTarget();
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
if (task.isLaunchTarget) {
launchTargetTask = task;
break;
}
}
// Prepare the first view for its enter animation
for (int i = taskViewCount - 1; i >= 0; i--) {
@@ -755,7 +775,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Task task = tv.getTask();
boolean occludesLaunchTarget = (launchTargetTask != null) &&
launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY);
tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget,
offscreenY);
}
// If the enter animation started already and we haven't completed a layout yet, do the
@@ -794,17 +815,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (mStack.getTaskCount() > 0) {
// Find the launch target task
Task launchTargetTask = null;
Task launchTargetTask = mStack.getLaunchTarget();
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
if (task.isLaunchTarget) {
launchTargetTask = task;
break;
}
}
// Animate all the task views into view
for (int i = taskViewCount - 1; i >= 0; i--) {
@@ -817,7 +830,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) &&
launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
ctx.updateListener = mRequestUpdateClippingListener;
mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null);
mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
ctx.currentTaskTransform, null);
tv.startEnterRecentsAnimation(ctx);
}
@@ -898,6 +912,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
protected void dispatchDraw(Canvas canvas) {
mLayersDisabled = false;
// Draw the freeform workspace background
if (mFreeformWorkspaceBackground.getAlpha() > 0) {
mFreeformWorkspaceBackground.draw(canvas);
}
super.dispatchDraw(canvas);
}
@@ -919,42 +939,44 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
Task newFrontMostTask) {
// 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);
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);
}
// 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 = null;
float prevAnchorTaskScroll = 0;
boolean pullStackForward = stack.getTaskCount() > 0;
if (pullStackForward) {
anchorTask = mStack.getFrontMostTask();
prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
}
// Update the min/max scroll and animate other task views into their new positions
updateMinMaxScroll(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 (pullStackForward) {
// Otherwise, offset the scroll by half the movement of the anchor task to allow the
// tasks behind the removed task to move forward, and the tasks in front to move back
float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
- prevAnchorTaskScroll) / 2);
mStackScroller.boundScroll();
}
// Animate all the tasks into place
requestSynchronizeStackViewsWithModel(200);
}
// 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 = null;
float prevAnchorTaskScroll = 0;
boolean pullStackForward = stack.getTaskCount() > 0;
if (pullStackForward) {
anchorTask = mStack.getFrontMostTask();
prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
}
// Update the min/max scroll and animate other task views into their new positions
updateMinMaxScroll(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 (pullStackForward) {
// Otherwise, offset the scroll by half the movement of the anchor task to allow the
// tasks behind the removed task to move forward, and the tasks in front to move back
float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
- prevAnchorTaskScroll) / 2);
mStackScroller.boundScroll();
}
// Animate all the tasks into place
requestSynchronizeStackViewsWithModel(200);
// Update the new front most task
if (newFrontMostTask != null) {
TaskView frontTv = getChildViewForTask(newFrontMostTask);
@@ -1103,7 +1125,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int insertIndex = -1;
int taskIndex = mStack.indexOfTask(task);
if (taskIndex != -1) {
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
@@ -1252,7 +1273,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// the next task
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
setFocusedTask(taskIndex - 1, true /* scrollToTask */, launchState.launchedWithAltTab);
setFocusedTask(taskIndex - 1,
!mStack.getTasks().get(taskIndex - 1).isFreeformTask() /* scrollToTask */,
launchState.launchedWithAltTab);
}
}
}

View File

@@ -39,7 +39,7 @@ public class TaskStackViewScroller {
}
Context mContext;
TaskStackViewLayoutAlgorithm mLayoutAlgorithm;
TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewScrollerCallbacks mCb;
float mStackScrollP;
@@ -52,7 +52,7 @@ public class TaskStackViewScroller {
Interpolator mLinearOutSlowInInterpolator;
public TaskStackViewScroller(Context context, TaskStackViewLayoutAlgorithm layoutAlgorithm) {
public TaskStackViewScroller(Context context, TaskStackLayoutAlgorithm layoutAlgorithm) {
mContext = context;
mScroller = new OverScroller(context);
mLayoutAlgorithm = layoutAlgorithm;
@@ -121,7 +121,8 @@ public class TaskStackViewScroller {
/** Returns the bounded stack scroll */
float getBoundedStackScroll(float scroll) {
return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));
return Math.max(mLayoutAlgorithm.mMinScrollP,
Math.min(mLayoutAlgorithm.getPreferredMaxScrollPosition(scroll), scroll));
}
/** Returns the amount that the absolute value of how much the scroll is out of bounds. */

View File

@@ -16,8 +16,10 @@
package com.android.systemui.recents.views;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -30,6 +32,8 @@ import com.android.systemui.recents.Constants;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.statusbar.FlingAnimationUtils;
import java.util.List;
@@ -37,7 +41,7 @@ import java.util.List;
class TaskStackViewTouchHandler implements SwipeHelper.Callback {
private static final String TAG = "TaskStackViewTouchHandler";
private static final boolean DEBUG = true;
private static final boolean DEBUG = false;
private static int INACTIVE_POINTER_ID = -1;
@@ -45,6 +49,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
TaskStackView mSv;
TaskStackViewScroller mScroller;
VelocityTracker mVelocityTracker;
FlingAnimationUtils mFlingAnimUtils;
ValueAnimator mScrollFlingAnimator;
boolean mIsScrolling;
float mDownScrollP;
@@ -74,6 +80,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
mSv = sv;
mScroller = scroller;
mFlingAnimUtils = new FlingAnimationUtils(context, 0.2f);
float densityScale = res.getDisplayMetrics().density;
mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll);
@@ -140,7 +147,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
return false;
}
TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
@@ -154,6 +161,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
// Stop the current scroll if it is still flinging
mScroller.stopScroller();
mScroller.stopBoundScrollAnimation();
Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator);
// Initialize the velocity tracker
initOrResetVelocityTracker();
@@ -189,6 +197,9 @@ 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);
}
}
mVelocityTracker.addMovement(ev);
@@ -211,21 +222,53 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int y = (int) ev.getY(activePointerIndex);
int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
float curScrollP = mScroller.getStackScroll();
if (mIsScrolling) {
if (mScroller.isScrollOutOfBounds()) {
// Animate the scroll back into bounds
boolean hasFreeformTasks = mSv.mStack.hasFreeformTasks();
if (hasFreeformTasks && velocity > 0 &&
curScrollP > layoutAlgorithm.mStackEndScrollP) {
// Snap to workspace
float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
layoutAlgorithm.mPreferredStackEndScrollP);
mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
(Integer) animation.getAnimatedValue());
float scroll = mDownScrollP + deltaP;
mScroller.setStackScroll(scroll);
}
});
mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
mScrollFlingAnimator.start();
} else if (hasFreeformTasks && velocity < 0 &&
curScrollP > (layoutAlgorithm.mStackEndScrollP -
layoutAlgorithm.mTaskHalfHeightPOffset)) {
// Snap to stack
float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
layoutAlgorithm.mMaxScrollP);
mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
(Integer) animation.getAnimatedValue());
float scroll = mDownScrollP + deltaP;
mScroller.setStackScroll(scroll);
}
});
mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
mScrollFlingAnimator.start();
} else if (mScroller.isScrollOutOfBounds()) {
mScroller.animateBoundScroll();
} else if (Math.abs(velocity) > mMinimumVelocity) {
float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
float curScrollP = mDownScrollP + deltaP;
float downToCurY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
curScrollP);
float downToMinY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
layoutAlgorithm.mMaxScrollP);
float downToMaxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
float minY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
layoutAlgorithm.mPreferredStackEndScrollP);
float maxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
layoutAlgorithm.mMinScrollP);
mScroller.fling(mDownScrollP, mDownY, (int) downToCurY, velocity,
(int) downToMinY, (int) downToMaxY, mOverscrollSize);
mScroller.fling(mDownScrollP, mDownY, y, velocity, (int) minY, (int) maxY,
mOverscrollSize);
mSv.invalidate();
}
} else if (mActiveTaskView == null) {
@@ -239,10 +282,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
break;
}
case MotionEvent.ACTION_CANCEL: {
if (mScroller.isScrollOutOfBounds()) {
// Animate the scroll back into bounds
mScroller.animateBoundScroll();
}
mActivePointerId = INACTIVE_POINTER_ID;
mIsScrolling = false;
recycleVelocityTracker();
@@ -308,9 +347,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
@Override
public boolean canChildBeDismissed(View v) {
if (v instanceof TaskView) {
return !((TaskView) v).getTask().isFreeformTask();
}
return true;
}

View File

@@ -552,7 +552,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
* view.
*/
boolean shouldClipViewInStack() {
return mClipViewInStack && (getVisibility() == View.VISIBLE);
// Never clip for freeform tasks or if invisible
if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) {
return false;
}
return mClipViewInStack;
}
/** Sets whether this view should be clipped, or clipped against. */

View File

@@ -255,6 +255,10 @@ public class TaskViewHeader extends FrameLayout
mApplicationIcon.setImageDrawable(null);
mApplicationIcon.setOnClickListener(null);
mMoveTaskButton.setOnClickListener(null);
// Stop any focus animations
Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
mBackground.jumpToCurrentState();
}
/** Updates the resize task bar button. */
@@ -370,8 +374,9 @@ public class TaskViewHeader extends FrameLayout
boolean isRunning = false;
if (mFocusAnimator != null) {
isRunning = mFocusAnimator.isRunning();
Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
}
Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
mBackground.jumpToCurrentState();
if (focused) {
// If we are not animating the visible state, just return