Fixing touch handling on resized tasks.

- Simplify touch handling just by keeping everything in screen space, 
  and just map to the curve proportionally to the arc length.  This
  makes the movement much closer to that of other lists, and prevents 
  the issue of touches at the top of the stack scrolling along the 
  curve faster than at other places (and when Recents is resized, this
  becomes more of an issue).
- Add basic functionality (disabled) for freeform task workspaces.


Change-Id: Ib3a8ce2caa1437cfc09eaa5b36aff310e29e35f5
This commit is contained in:
Winson
2015-10-28 11:14:54 -07:00
parent e92440ee5f
commit 23afcae844
11 changed files with 349 additions and 268 deletions

View File

@@ -230,6 +230,9 @@
<!-- The size of the lock-to-app button icon. -->
<dimen name="recents_lock_to_app_icon_size">28dp</dimen>
<!-- The amount to allow the stack to overscroll. -->
<dimen name="recents_stack_overscroll">24dp</dimen>
<!-- Space reserved for the cards behind the top card in the top stack -->
<dimen name="top_stack_peek_amount">12dp</dimen>

View File

@@ -64,8 +64,6 @@ public class Constants {
}
public static class TaskStackView {
public static final int TaskStackMinOverscrollRange = 32;
public static final int TaskStackMaxOverscrollRange = 128;
public static final int FilterStartDelay = 25;
}
}

View File

@@ -469,7 +469,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
Rect taskStackBounds = new Rect(mTaskStackBounds);
algo.setSystemInsets(systemInsets);
algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
algo.computeRects(taskStackBounds);
Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
if (!taskViewBounds.equals(mLastTaskViewBounds)) {
mLastTaskViewBounds.set(taskViewBounds);

View File

@@ -48,6 +48,7 @@ public class ParametricCurve {
float[] xp;
float[] px;
float mLength;
CurveFunction mFn;
ParametricCurveFunction mScaleFn;
@@ -79,6 +80,7 @@ public class ParametricCurve {
dx[xStep] = (float) Math.hypot(fx[xStep] - fx[xStep - 1], step);
pLength += dx[xStep];
}
mLength = pLength;
// Approximate p(x), a function of cumulative progress with x, normalized to 0..1
float p = 0;
px[0] = 0f;
@@ -260,4 +262,11 @@ public class ParametricCurve {
}
return 1f - xToP(maxX, bounds);
}
/**
* Returns the length of this curve.
*/
public float getArcLength() {
return mLength;
}
}

View File

@@ -170,6 +170,12 @@ public class Task {
}
}
public boolean isFreeformTask() {
// Temporarily disable:
return false;
// return SystemServicesProxy.isFreeformStack(key.stackId);
}
/** Notifies the callback listeners that this task has been loaded */
public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
this.applicationIcon = applicationIcon;

View File

@@ -68,6 +68,7 @@ public class SwipeHelper {
private float mInitialTouchPos;
private boolean mDragging;
private float mSnapBackTranslationX;
private View mCurrView;
private boolean mCanCurrViewBeDimissed;
@@ -92,6 +93,10 @@ public class SwipeHelper {
mDensityScale = densityScale;
}
public void setSnapBackTranslationX(float translationX) {
mSnapBackTranslationX = translationX;
}
public void setPagingTouchSlop(float pagingTouchSlop) {
mPagingTouchSlop = pagingTouchSlop;
}
@@ -267,7 +272,7 @@ public class SwipeHelper {
private void snapChild(final View view, float velocity) {
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
ValueAnimator anim = createTranslationAnimation(view, 0);
ValueAnimator anim = createTranslationAnimation(view, mSnapBackTranslationX);
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
anim.setInterpolator(mLinearOutSlowInInterpolator);

View File

@@ -124,7 +124,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mConfig = RecentsConfiguration.getInstance();
mViewPool = new ViewPool<>(context, this);
mInflater = LayoutInflater.from(context);
mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context, mConfig);
mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context);
mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool);
mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm);
mStackScroller.setCallbacks(this);
@@ -633,10 +633,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/** Computes the stack and task rects */
public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds,
boolean launchedWithAltTab, boolean launchedFromHome) {
public void computeRects(Rect taskStackBounds) {
// Compute the rects in the stack algorithm
mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);
mLayoutAlgorithm.computeRects(taskStackBounds);
// Update the scroll bounds
updateMinMaxScroll(false);
@@ -674,9 +673,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int height = MeasureSpec.getSize(heightMeasureSpec);
// Compute our stack/task rects
RecentsActivityLaunchState launchState = mConfig.getLaunchState();
computeRects(width, height, mTaskStackBounds, launchState.launchedWithAltTab,
launchState.launchedFromHome);
computeRects(mTaskStackBounds);
// If this is the first layout, then scroll to the front of the stack and synchronize the
// stack views immediately to load all the views

View File

@@ -21,6 +21,7 @@ import android.content.res.Resources;
import android.graphics.Rect;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.ParametricCurve;
import com.android.systemui.recents.misc.Utilities;
@@ -32,14 +33,15 @@ import java.util.HashMap;
/**
* The layout logic for a TaskStackView.
*
*/
public class TaskStackViewLayoutAlgorithm {
private static final boolean DEBUG = false;
private static final String TAG = "TaskStackViewLayoutAlgorithm";
private static final boolean DEBUG = false;
// The min scale of the last task at the top of the curve
private static final float STACK_PEEK_MIN_SCALE = 0.75f;
private static final float STACK_PEEK_MIN_SCALE = 0.85f;
// The scale of the last task
private static final float SINGLE_TASK_SCALE = 0.95f;
// The percentage of height of task to show between tasks
@@ -58,14 +60,17 @@ public class TaskStackViewLayoutAlgorithm {
}
Context mContext;
RecentsConfiguration mConfig;
// 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
public Rect mSystemInsets = new Rect();
@@ -74,8 +79,13 @@ 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
float mInitialScrollP;
// The task progress for the front-most task in the stack
float mFrontMostTaskP;
// The relative progress to ensure that the height between affiliated tasks is respected
float mWithinAffiliationPOffset;
@@ -89,11 +99,21 @@ public class TaskStackViewLayoutAlgorithm {
// 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 mFreeformWorkspacePOffset;
// The front-most task bottom offset
int mTaskBottomOffset;
// The last computed task count
int mNumTasks;
// 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;
int mNumFreeformTasks;
// The min/max z translations
int mMinTranslationZ;
int mMaxTranslationZ;
@@ -104,12 +124,11 @@ public class TaskStackViewLayoutAlgorithm {
// Log function
static ParametricCurve sCurve;
public TaskStackViewLayoutAlgorithm(Context context, RecentsConfiguration config) {
public TaskStackViewLayoutAlgorithm(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;
mConfig = config;
if (sCurve == null) {
sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() {
// The large the XScale, the longer the flat area of the curve
@@ -153,23 +172,25 @@ public class TaskStackViewLayoutAlgorithm {
}
/**
* Computes the stack and task rects
* Computes the stack and task rects.
*/
public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * taskStackBounds.width());
public void computeRects(Rect taskStackBounds) {
RecentsConfiguration config = RecentsConfiguration.getInstance();
int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
int heightPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_stack_top_padding);
// Compute the stack rect, inset from the given task stack bounds
mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding,
taskStackBounds.right - widthPadding, windowHeight);
taskStackBounds.right - widthPadding, taskStackBounds.bottom);
mTaskBottomOffset = mSystemInsets.bottom + heightPadding;
// Compute the task rect, align it to the top-center square in the stack rect
int size = Math.min(mStackRect.width(), taskStackBounds.height() - mTaskBottomOffset);
int size = Math.min(mStackRect.width(), mStackRect.height() - mTaskBottomOffset);
int xOffset = (mStackRect.width() - size) / 2;
mTaskRect.set(mStackRect.left + xOffset, mStackRect.top,
mStackRect.right - xOffset, mStackRect.top + size);
mFreeformRect.set(mTaskRect);
// Compute the progress offsets
int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
@@ -184,9 +205,12 @@ public class TaskStackViewLayoutAlgorithm {
mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
mStackRect);
mTaskBottomPOffset = sCurve.computePOffsetForHeight(mTaskBottomOffset, mStackRect);
mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformRect.height(),
mStackRect);
if (DEBUG) {
Log.d(TAG, "computeRects");
Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
Log.d(TAG, "\tmStackRect: " + mStackRect);
Log.d(TAG, "\tmTaskRect: " + mTaskRect);
Log.d(TAG, "\tmSystemInsets: " + mSystemInsets);
@@ -222,29 +246,38 @@ public class TaskStackViewLayoutAlgorithm {
// Clear the progress map
mTaskProgressMap.clear();
mNumTasks = tasks.size();
// Return early if we have no tasks
if (tasks.isEmpty()) {
mMinScrollP = mMaxScrollP = 0;
mMinScrollP = mMaxScrollP = mMaxStackScrollP = 0;
mNumStackTasks = mNumFreeformTasks = 0;
return;
}
// We calculate the progress by taking the progress of the element from the bottom of the
// screen
if (mNumTasks == 1) {
// Just center the task in the visible stack rect
mMinScrollP = mMaxScrollP = mInitialScrollP = 0f;
mTaskProgressMap.put(tasks.get(0).key, 0f);
} else {
// Update the tasks from back to front with the new progresses. We set the initial
// progress to the progress at which the top of the last task is near the center of the
// visible stack rect.
// Filter the set of freeform and stack tasks
ArrayList<Task> freeformTasks = new ArrayList<>();
ArrayList<Task> stackTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if (task.isFreeformTask()) {
freeformTasks.add(task);
} else {
stackTasks.add(task);
}
}
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;
float pAtFrontMostTaskTop = pAtBackMostTaskTop;
int taskCount = tasks.size();
int taskCount = stackTasks.size();
for (int i = 0; i < taskCount; i++) {
Task task = tasks.get(i);
Task task = stackTasks.get(i);
mTaskProgressMap.put(task.key, pAtFrontMostTaskTop);
if (i < (taskCount - 1)) {
@@ -255,15 +288,47 @@ public class TaskStackViewLayoutAlgorithm {
}
}
// Set the max scroll progress to the point at which the bottom of the front-most task
// is aligned to the bottom of the stack (including nav bar and stack padding)
mMaxScrollP = pAtFrontMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset;
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);
// Basically align the back-most task such that its progress is the same as the top of
// the front most task at the max scroll
mMinScrollP = pAtBackMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset;
// The offset the inital scroll position to the front of the stack, with half the front
// task height visible
mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
// the front most task at the max stack scroll
mMinScrollP = getBottomAlignedScrollProgress(pAtBackMostTaskTop,
mTaskBottomPOffset + mTaskHeightPOffset);
}
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
// workspace will be visible
mMaxScrollP = mMaxStackScrollP + mFreeformWorkspacePOffset;
mInitialScrollP = mMaxScrollP;
} else {
mMaxScrollP = mMaxStackScrollP;
mInitialScrollP = Math.max(mMinScrollP, mMaxStackScrollP - 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, "mMaxScrollP: " + mMaxScrollP);
}
}
@@ -272,11 +337,24 @@ public class TaskStackViewLayoutAlgorithm {
* computeMinMaxScroll() is called first.
*/
public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
// Ensure minimum visibility count
if (tasks.size() <= 1) {
return new VisibilityReport(1, 1);
}
// Walk backwards in the task stack and count the number of tasks and visible thumbnails
// If there are freeform tasks, then they will be the only ones visible
int freeformTaskCount = 0;
for (Task t : tasks) {
if (t.isFreeformTask()) {
freeformTaskCount++;
}
}
if (freeformTaskCount > 0) {
return new VisibilityReport(freeformTaskCount, freeformTaskCount);
}
// Otherwise, walk backwards in the stack and count the number of tasks and visible
// thumbnails
int taskHeight = mTaskRect.height();
int taskBarHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_bar_height);
@@ -338,18 +416,38 @@ public class TaskStackViewLayoutAlgorithm {
/** Update/get the transform */
public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform prevTransform) {
if (DEBUG) {
Log.d(TAG, "getStackTransform: " + stackScroll);
float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset;
int overscrollYOffset = 0;
if (mNumFreeformTasks > 0) {
overscrollYOffset = (int) (Math.max(0, stackOverscroll) * mFreeformRect.height());
}
if (mNumTasks == 1) {
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) {
// Center the task in the stack, changing the scale will not follow the curve, but just
// modulate some values directly
float pTaskRelative = -stackScroll;
float pTaskRelative = mMinScrollP - stackScroll;
float scale = SINGLE_TASK_SCALE;
int topOffset = (mStackRect.height() - mTaskBottomOffset - mTaskRect.height()) / 2;
transformOut.scale = scale;
transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height()));
transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height())) -
overscrollYOffset;
transformOut.translationZ = mMaxTranslationZ;
transformOut.rect.set(mTaskRect);
transformOut.rect.offset(0, transformOut.translationY);
@@ -357,8 +455,12 @@ public class TaskStackViewLayoutAlgorithm {
transformOut.visible = true;
transformOut.p = pTaskRelative;
return transformOut;
} else {
float pTaskRelative = taskProgress - stackScroll;
if (mNumFreeformTasks > 0) {
pTaskRelative = Math.min(mMaxStackScrollP, pTaskRelative);
}
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) {
@@ -379,7 +481,7 @@ public class TaskStackViewLayoutAlgorithm {
int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
transformOut.scale = scale;
transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top -
scaleYOffset;
scaleYOffset - overscrollYOffset;
transformOut.translationZ = Math.max(mMinTranslationZ,
mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ)));
transformOut.rect.set(mTaskRect);
@@ -391,6 +493,27 @@ public class TaskStackViewLayoutAlgorithm {
}
}
/**
* Update/get the transform
*/
public TaskViewTransform getFreeformWorkspaceBounds(float stackScroll,
TaskViewTransform transformOut) {
transformOut.reset();
if (mNumFreeformTasks == 0) {
return transformOut;
}
if (stackScroll > mMaxStackScrollP) {
float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset;
int overscrollYOffset = (int) (stackOverscroll * mFreeformRect.height());
int frontTaskBottom = mStackRect.height() - mTaskBottomOffset;
transformOut.visible = true;
transformOut.alpha =
transformOut.translationY = frontTaskBottom - overscrollYOffset;
}
return transformOut;
}
/**
* Returns the untransformed task view bounds.
*/
@@ -406,4 +529,29 @@ public class TaskStackViewLayoutAlgorithm {
if (!mTaskProgressMap.containsKey(t.key)) return 0f;
return mTaskProgressMap.get(t.key);
}
/**
* Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc
* length of the curve. We know the curve is mostly flat, so we just map the length of the
* screen along the arc-length proportionally (1/arclength).
*/
public float getDeltaPForY(int downY, int y) {
float deltaP = (float) (y - downY) / mStackRect.height() * (1f / sCurve.getArcLength());
return -deltaP;
}
/**
* This is the inverse of {@link #getDeltaPForY}. Given a movement along the arc length
* of the curve, map back to the screen y.
*/
public int getYForDeltaP(float downScrollP, float p) {
int y = (int) ((p - downScrollP) * mStackRect.height() * sCurve.getArcLength());
return -y;
}
private float getBottomAlignedScrollProgress(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

@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.Log;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.OverScroller;
@@ -29,8 +30,12 @@ import com.android.systemui.recents.misc.Utilities;
/* The scrolling logic for a TaskStackView */
public class TaskStackViewScroller {
private static final String TAG = "TaskStackViewScroller";
private static final boolean DEBUG = false;
public interface TaskStackViewScrollerCallbacks {
public void onScrollChanged(float p);
void onScrollChanged(float p);
}
Context mContext;
@@ -38,6 +43,8 @@ public class TaskStackViewScroller {
TaskStackViewScrollerCallbacks mCb;
float mStackScrollP;
float mFlingDownScrollP;
int mFlingDownY;
OverScroller mScroller;
ObjectAnimator mScrollAnimator;
@@ -77,11 +84,6 @@ public class TaskStackViewScroller {
}
}
/** Sets the current stack scroll without calling the callback. */
void setStackScrollRaw(float s) {
mStackScrollP = s;
}
/**
* Sets the current stack scroll to the initial state when you first enter recents.
* @return whether the stack progress changed.
@@ -92,6 +94,20 @@ public class TaskStackViewScroller {
return Float.compare(prevStackScrollP, mStackScrollP) != 0;
}
/**
* Starts a fling that is coordinated with the {@link TaskStackViewTouchHandler}.
*/
public void fling(float downScrollP, int downY, int y, int velY, int minY, int maxY,
int overscroll) {
if (DEBUG) {
Log.d(TAG, "fling: " + downScrollP + ", downY: " + downY + ", y: " + y +
", velY: " + velY + ", minY: " + minY + ", maxY: " + maxY);
}
mFlingDownScrollP = downScrollP;
mFlingDownY = downY;
mScroller.fling(0, y, 0, velY, 0, 0, minY, maxY, 0, overscroll);
}
/** Bounds the current scroll if necessary */
public boolean boundScroll() {
float curScroll = getStackScroll();
@@ -174,21 +190,20 @@ public class TaskStackViewScroller {
/**** OverScroller ****/
// TODO: Remove
@Deprecated
int progressToScrollRange(float p) {
return (int) (p * mLayoutAlgorithm.mStackRect.height());
}
float scrollRangeToProgress(int s) {
return (float) s / mLayoutAlgorithm.mStackRect.height();
}
/** Called from the view draw, computes the next scroll. */
boolean computeScroll() {
if (mScroller.computeScrollOffset()) {
float scroll = scrollRangeToProgress(mScroller.getCurrY());
setStackScrollRaw(scroll);
if (mCb != null) {
mCb.onScrollChanged(scroll);
float deltaP = mLayoutAlgorithm.getDeltaPForY(mFlingDownY, mScroller.getCurrY());
float scroll = mFlingDownScrollP + deltaP;
setStackScroll(scroll);
if (DEBUG) {
Log.d(TAG, "computeScroll: " + scroll);
}
return true;
}

View File

@@ -17,6 +17,7 @@
package com.android.systemui.recents.views;
import android.content.Context;
import android.content.res.Resources;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -34,7 +35,11 @@ import java.util.List;
/* Handles touch events for a TaskStackView. */
class TaskStackViewTouchHandler implements SwipeHelper.Callback {
static int INACTIVE_POINTER_ID = -1;
private static final String TAG = "TaskStackViewTouchHandler";
private static final boolean DEBUG = true;
private static int INACTIVE_POINTER_ID = -1;
Context mContext;
TaskStackView mSv;
@@ -42,21 +47,16 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
VelocityTracker mVelocityTracker;
boolean mIsScrolling;
float mInitialP;
float mLastP;
float mTotalPMotion;
int mInitialMotionX, mInitialMotionY;
int mLastMotionX, mLastMotionY;
float mDownScrollP;
int mDownX, mDownY;
int mActivePointerId = INACTIVE_POINTER_ID;
int mOverscrollSize;
TaskView mActiveTaskView = null;
int mMinimumVelocity;
int mMaximumVelocity;
// The scroll touch slop is used to calculate when we start scrolling
int mScrollTouchSlop;
// The page touch slop is used to calculate when we start swiping
float mPagingTouchSlop;
// Used to calculate when a tap is outside a task view rectangle.
final int mWindowTouchSlop;
@@ -65,18 +65,20 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
public TaskStackViewTouchHandler(Context context, TaskStackView sv,
TaskStackViewScroller scroller) {
mContext = context;
Resources res = context.getResources();
ViewConfiguration configuration = ViewConfiguration.get(context);
mContext = context;
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mScrollTouchSlop = configuration.getScaledTouchSlop();
mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
mSv = sv;
mScroller = scroller;
float densityScale = context.getResources().getDisplayMetrics().density;
mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale, mPagingTouchSlop);
float densityScale = res.getDisplayMetrics().density;
mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll);
mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale,
configuration.getScaledPagingTouchSlop());
mSwipeHelper.setMinAlpha(1f);
}
@@ -88,11 +90,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
mVelocityTracker.clear();
}
}
void initVelocityTrackerIfNotExists() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
}
void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
@@ -115,175 +112,70 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
return null;
}
/** Constructs a simulated motion event for the current stack scroll. */
MotionEvent createMotionEventForStackScroll(MotionEvent ev) {
MotionEvent pev = MotionEvent.obtainNoHistory(ev);
pev.setLocation(0, mScroller.progressToScrollRange(mScroller.getStackScroll()));
return pev;
}
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Return early if we have no children
boolean hasTaskViews = (mSv.getTaskViews().size() > 0);
if (!hasTaskViews) {
return false;
}
// Pass through to swipe helper if we are swiping
mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
if (mInterceptedBySwipeHelper) {
return true;
}
TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
boolean wasScrolling = mScroller.isScrolling() ||
(mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
mInitialMotionX = mLastMotionX = (int) ev.getX();
mInitialMotionY = mLastMotionY = (int) ev.getY();
mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
layoutAlgorithm.mStackRect);
mActivePointerId = ev.getPointerId(0);
mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
// Stop the current scroll if it is still flinging
mScroller.stopScroller();
mScroller.stopBoundScrollAnimation();
// Initialize the velocity tracker
initOrResetVelocityTracker();
mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
mActivePointerId = ev.getPointerId(index);
mLastMotionX = (int) ev.getX(index);
mLastMotionY = (int) ev.getY(index);
mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
break;
}
case MotionEvent.ACTION_MOVE: {
if (mActivePointerId == INACTIVE_POINTER_ID) break;
// Initialize the velocity tracker if necessary
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int y = (int) ev.getY(activePointerIndex);
int x = (int) ev.getX(activePointerIndex);
if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
// Save the touch move info
mIsScrolling = true;
// Disallow parents from intercepting touch events
final ViewParent parent = mSv.getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
mLastMotionX = x;
mLastMotionY = y;
mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
break;
}
case MotionEvent.ACTION_POINTER_UP: {
int pointerIndex = ev.getActionIndex();
int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// Select a new active pointer id and reset the motion state
final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
mLastMotionX = (int) ev.getX(newPointerIndex);
mLastMotionY = (int) ev.getY(newPointerIndex);
mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
mVelocityTracker.clear();
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
// Animate the scroll back if we've cancelled
mScroller.animateBoundScroll();
// Reset the drag state and the velocity tracker
mIsScrolling = false;
mActivePointerId = INACTIVE_POINTER_ID;
mActiveTaskView = null;
mTotalPMotion = 0;
recycleVelocityTracker();
break;
}
}
return wasScrolling || mIsScrolling;
return handleTouchEvent(ev);
}
/** Handles touch events once we have intercepted them */
public boolean onTouchEvent(MotionEvent ev) {
// Short circuit if we have no children
boolean hasTaskViews = (mSv.getTaskViews().size() > 0);
if (!hasTaskViews) {
return false;
}
// Pass through to swipe helper if we are swiping
if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
return true;
}
// Update the velocity tracker
initVelocityTrackerIfNotExists();
handleTouchEvent(ev);
return true;
}
private boolean handleTouchEvent(MotionEvent ev) {
// Short circuit if we have no children
if (mSv.getTaskViews().size() == 0) {
return false;
}
TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
mInitialMotionX = mLastMotionX = (int) ev.getX();
mInitialMotionY = mLastMotionY = (int) ev.getY();
mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
layoutAlgorithm.mStackRect);
mDownX = (int) ev.getX();
mDownY = (int) ev.getY();
mDownScrollP = mScroller.getStackScroll();
mActivePointerId = ev.getPointerId(0);
mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
mActiveTaskView = findViewAtPoint(mDownX, mDownY);
// Stop the current scroll if it is still flinging
mScroller.stopScroller();
mScroller.stopBoundScrollAnimation();
// Initialize the velocity tracker
initOrResetVelocityTracker();
mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
// Disallow parents from intercepting touch events
final ViewParent parent = mSv.getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
mVelocityTracker.addMovement(ev);
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
mDownX = (int) ev.getX();
mDownY = (int) ev.getY();
mDownScrollP = mScroller.getStackScroll();
mActivePointerId = ev.getPointerId(index);
mLastMotionX = (int) ev.getX(index);
mLastMotionY = (int) ev.getY(index);
mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
layoutAlgorithm.mStackRect);
mVelocityTracker.addMovement(ev);
break;
}
case MotionEvent.ACTION_MOVE: {
if (mActivePointerId == INACTIVE_POINTER_ID) break;
mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int x = (int) ev.getX(activePointerIndex);
int y = (int) ev.getY(activePointerIndex);
int yTotal = Math.abs(y - mInitialMotionY);
float curP = layoutAlgorithm.sCurve.xToP(y, layoutAlgorithm.mStackRect);
float deltaP = mLastP - curP;
if (!mIsScrolling) {
if (yTotal > mScrollTouchSlop) {
if (Math.abs(y - mDownY) > mScrollTouchSlop) {
mIsScrolling = true;
// Disallow parents from intercepting touch events
final ViewParent parent = mSv.getParent();
if (parent != null) {
@@ -292,54 +184,14 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
}
}
if (mIsScrolling) {
float curStackScroll = mScroller.getStackScroll();
float overScrollAmount = mScroller.getScrollAmountOutOfBounds(curStackScroll + deltaP);
if (Float.compare(overScrollAmount, 0f) != 0) {
// Bound the overscroll to a fixed amount, and inversely scale the y-movement
// relative to how close we are to the max overscroll
float maxOverScroll = mContext.getResources().getFloat(
R.dimen.recents_stack_overscroll_percentage);
deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount)
/ maxOverScroll));
}
mScroller.setStackScroll(curStackScroll + deltaP);
}
mLastMotionX = x;
mLastMotionY = y;
mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
mTotalPMotion += Math.abs(deltaP);
break;
}
case MotionEvent.ACTION_UP: {
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity);
int overscrollRange = (int) (Math.min(1f, overscrollRangePct) *
(Constants.Values.TaskStackView.TaskStackMaxOverscrollRange -
Constants.Values.TaskStackView.TaskStackMinOverscrollRange));
mScroller.mScroller.fling(0,
mScroller.progressToScrollRange(mScroller.getStackScroll()),
0, velocity,
0, 0,
mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMinScrollP),
mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMaxScrollP),
0, Constants.Values.TaskStackView.TaskStackMinOverscrollRange +
overscrollRange);
// Invalidate to kick off computeScroll
mSv.invalidate();
} else if (mIsScrolling && mScroller.isScrollOutOfBounds()) {
// Animate the scroll back into bounds
mScroller.animateBoundScroll();
} else if (mActiveTaskView == null) {
// This tap didn't start on a task.
maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY());
// If we just move linearly on the screen, then that would map to 1/arclength
// of the curve, so just move the scroll proportional to that
float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
float curScrollP = mDownScrollP + deltaP;
mScroller.setStackScroll(curScrollP);
}
mActivePointerId = INACTIVE_POINTER_ID;
mIsScrolling = false;
mTotalPMotion = 0;
recycleVelocityTracker();
mVelocityTracker.addMovement(ev);
break;
}
case MotionEvent.ACTION_POINTER_UP: {
@@ -349,11 +201,41 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
// Select a new active pointer id and reset the motion state
final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
mLastMotionX = (int) ev.getX(newPointerIndex);
mLastMotionY = (int) ev.getY(newPointerIndex);
mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
mVelocityTracker.clear();
}
mVelocityTracker.addMovement(ev);
break;
}
case MotionEvent.ACTION_UP: {
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int y = (int) ev.getY(activePointerIndex);
int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
if (mIsScrolling) {
if (mScroller.isScrollOutOfBounds()) {
// Animate the scroll back into bounds
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,
layoutAlgorithm.mMinScrollP);
mScroller.fling(mDownScrollP, mDownY, (int) downToCurY, velocity,
(int) downToMinY, (int) downToMaxY, mOverscrollSize);
mSv.invalidate();
}
} else if (mActiveTaskView == null) {
// This tap didn't start on a task.
maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY());
}
mActivePointerId = INACTIVE_POINTER_ID;
mIsScrolling = false;
recycleVelocityTracker();
break;
}
case MotionEvent.ACTION_CANCEL: {
@@ -363,20 +245,19 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
}
mActivePointerId = INACTIVE_POINTER_ID;
mIsScrolling = false;
mTotalPMotion = 0;
recycleVelocityTracker();
break;
}
}
return true;
return mIsScrolling;
}
/** Hides recents if the up event at (x, y) is a tap on the background area. */
void maybeHideRecentsFromBackgroundTap(int x, int y) {
// Ignore the up event if it's too far from its start position. The user might have been
// trying to scroll or swipe.
int dx = Math.abs(mInitialMotionX - x);
int dy = Math.abs(mInitialMotionY - y);
int dx = Math.abs(mDownX - x);
int dy = Math.abs(mDownY - y);
if (dx > mScrollTouchSlop || dy > mScrollTouchSlop) {
return;
}
@@ -427,12 +308,16 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
@Override
public boolean canChildBeDismissed(View v) {
if (v instanceof TaskView) {
return !((TaskView) v).getTask().isFreeformTask();
}
return true;
}
@Override
public void onBeginDrag(View v) {
TaskView tv = (TaskView) v;
mSwipeHelper.setSnapBackTranslationX(tv.getTranslationX());
// Disable clipping with the stack while we are swiping
tv.setClipViewInStack(false);
// Disallow touch events from this task view

View File

@@ -26,6 +26,7 @@ import android.view.animation.Interpolator;
/* The transform state for a task view */
public class TaskViewTransform {
public int startDelay = 0;
public int translationX = 0;
public int translationY = 0;
public float translationZ = 0;
public float scale = 1f;
@@ -43,6 +44,7 @@ public class TaskViewTransform {
public TaskViewTransform(TaskViewTransform o) {
startDelay = o.startDelay;
translationX = o.translationX;
translationY = o.translationY;
translationZ = o.translationZ;
scale = o.scale;
@@ -52,9 +54,12 @@ public class TaskViewTransform {
p = o.p;
}
/** Resets the current transform */
/**
* Resets the current transform.
*/
public void reset() {
startDelay = 0;
translationX = 0;
translationY = 0;
translationZ = 0;
scale = 1f;
@@ -71,6 +76,9 @@ public class TaskViewTransform {
public boolean hasScaleChangedFrom(float v) {
return (Float.compare(scale, v) != 0);
}
public boolean hasTranslationXChangedFrom(float v) {
return (Float.compare(translationX, v) != 0);
}
public boolean hasTranslationYChangedFrom(float v) {
return (Float.compare(translationY, v) != 0);
}
@@ -87,6 +95,9 @@ public class TaskViewTransform {
boolean requiresLayers = false;
// Animate to the final state
if (hasTranslationXChangedFrom(v.getTranslationX())) {
anim.translationX(translationX);
}
if (hasTranslationYChangedFrom(v.getTranslationY())) {
anim.translationY(translationY);
}
@@ -117,6 +128,9 @@ public class TaskViewTransform {
.start();
} else {
// Set the changed properties
if (hasTranslationXChangedFrom(v.getTranslationX())) {
v.setTranslationX(translationX);
}
if (hasTranslationYChangedFrom(v.getTranslationY())) {
v.setTranslationY(translationY);
}
@@ -147,7 +161,8 @@ public class TaskViewTransform {
@Override
public String toString() {
return "TaskViewTransform delay: " + startDelay + " y: " + translationY + " z: " + translationZ +
return "TaskViewTransform delay: " + startDelay +
" x: " + translationX + " y: " + translationY + " z: " + translationZ +
" scale: " + scale + " alpha: " + alpha + " visible: " + visible + " rect: " + rect +
" p: " + p;
}