From 23afcae84422ac7c9687b17a6193f74264361303 Mon Sep 17 00:00:00 2001 From: Winson Date: Wed, 28 Oct 2015 11:14:54 -0700 Subject: [PATCH] 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 --- packages/SystemUI/res/values/dimens.xml | 3 + .../android/systemui/recents/Constants.java | 2 - .../android/systemui/recents/RecentsImpl.java | 2 +- .../recents/misc/ParametricCurve.java | 9 + .../android/systemui/recents/model/Task.java | 6 + .../systemui/recents/views/SwipeHelper.java | 7 +- .../systemui/recents/views/TaskStackView.java | 11 +- .../views/TaskStackViewLayoutAlgorithm.java | 230 +++++++++++--- .../recents/views/TaskStackViewScroller.java | 43 ++- .../views/TaskStackViewTouchHandler.java | 285 ++++++------------ .../recents/views/TaskViewTransform.java | 19 +- 11 files changed, 349 insertions(+), 268 deletions(-) diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index cbc92f2d985b3..bd7f45bd0e7a5 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -230,6 +230,9 @@ 28dp + + 24dp + 12dp diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index 6668df93511d6..ebfacac1b1fb8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -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; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index b05e1fe4adbaf..23836ab762165 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -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); diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java index 8e85bfc4aca6f..ea6821fe16961 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java @@ -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; + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 0793180549a9a..60051b83ad437 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java index e04699c10fddd..b7c1de36279fb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java @@ -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); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 757e695f283bf..21ceb338677d1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java index 9a5d9bdf79021..d81f3f6ea6a59 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java @@ -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 freeformTasks = new ArrayList<>(); + ArrayList 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 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); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index 3d3b13dd3290c..15fcab4c67cd6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -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; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 3a1a9876926f6..08889c53161cc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index ec50eb61b5585..174ff33e96814 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -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; }