-> Refactored and cleaned up dragging / animations
-> Added methods to generate blue glow outline
-> Added "res-out" effect
-> Added some influence for dragging left / right
Change-Id: I4bdbe4c3bd843ed5616b1ea359a3b0af1c151814
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 2b723c9..ef00d88 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -685,14 +685,6 @@
verticalOffset = 0;
}
- void setHorizontalOffset(int newHorizontalOffset) {
- horizontalOffset = newHorizontalOffset;
- if (mView != null) {
- mView.requestLayout();
- mView.invalidate();
- }
- }
-
private Rect parentRect = new Rect();
void invalidateGlobalRegion(View v, Rect r) {
View p = v;
@@ -722,5 +714,17 @@
invalidateGlobalRegion(mView, invalidateRect);
}
}
+
+ public void setHorizontalOffset(int newHorizontalOffset) {
+ int offsetDelta = newHorizontalOffset - horizontalOffset;
+ horizontalOffset = newHorizontalOffset;
+ if (mView != null) {
+ mView.requestLayout();
+ int left = Math.min(mView.getLeft() + offsetDelta, mView.getLeft());
+ int right = Math.max(mView.getRight() + offsetDelta, mView.getRight());
+ invalidateRect.set(left, mView.getTop(), right, mView.getBottom());
+ invalidateGlobalRegion(mView, invalidateRect);
+ }
+ }
}
}
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 4cd44d9..e3aca6a 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -20,6 +20,12 @@
import android.animation.PropertyAnimator;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
@@ -72,11 +78,9 @@
* These variables are all related to the current state of touch interaction
* with the stack
*/
- private boolean mGestureComplete = false;
private float mInitialY;
private float mInitialX;
private int mActivePointerId;
- private int mYOffset = 0;
private int mYVelocity = 0;
private int mSwipeGestureType = GESTURE_NONE;
private int mViewHeight;
@@ -85,6 +89,8 @@
private int mMaximumVelocity;
private VelocityTracker mVelocityTracker;
+ private ImageView mHighlight;
+ private StackSlider mStackSlider;
private boolean mFirstLayoutHappened = false;
// TODO: temp hack to get this thing started
@@ -107,6 +113,15 @@
mTouchSlop = configuration.getScaledTouchSlop();// + 5;
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mActivePointerId = INVALID_POINTER;
+
+ mHighlight = new ImageView(getContext());
+ mHighlight.setLayoutParams(new LayoutParams(mHighlight));
+ addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight));
+ mStackSlider = new StackSlider();
+
+ if (!sPaintsInitialized) {
+ initializePaints(getContext());
+ }
}
/**
@@ -124,6 +139,7 @@
} else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) {
// Slide item in
view.setVisibility(VISIBLE);
+
LayoutParams lp = (LayoutParams) view.getLayoutParams();
int largestDuration = (int) Math.round(
@@ -136,19 +152,18 @@
duration = Math.min(duration, largestDuration);
duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);
- PropertyAnimator slideDown = new PropertyAnimator(duration, lp,
- "verticalOffset", lp.verticalOffset, 0);
- slideDown.start();
+ PropertyAnimator slideInY = new PropertyAnimator(duration, mStackSlider,
+ "YProgress", mStackSlider.getYProgress(), 0);
+ slideInY.start();
+ PropertyAnimator slideInX = new PropertyAnimator(duration, mStackSlider,
+ "XProgress", mStackSlider.getXProgress(), 0);
+ slideInX.start();
- PropertyAnimator fadeIn = new PropertyAnimator(duration, view,
- "alpha", view.getAlpha(), 1.0f);
- fadeIn.start();
} else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) {
// Slide item out
LayoutParams lp = (LayoutParams) view.getLayoutParams();
- int largestDuration = (int) Math.round(
- (1 - (lp.verticalOffset*1.0f/-mViewHeight))*DEFAULT_ANIMATION_DURATION);
+ int largestDuration = (int) Math.round(mStackSlider.getYProgress()*DEFAULT_ANIMATION_DURATION);
int duration = largestDuration;
if (mYVelocity != 0) {
duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity);
@@ -157,13 +172,13 @@
duration = Math.min(duration, largestDuration);
duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);
- PropertyAnimator slideUp = new PropertyAnimator(duration, lp,
- "verticalOffset", lp.verticalOffset, -mViewHeight);
- slideUp.start();
+ PropertyAnimator slideOutY = new PropertyAnimator(duration, mStackSlider,
+ "YProgress", mStackSlider.getYProgress(), 1);
+ slideOutY.start();
+ PropertyAnimator slideOutX = new PropertyAnimator(duration, mStackSlider,
+ "XProgress", mStackSlider.getXProgress(), 0);
+ slideOutX.start();
- PropertyAnimator fadeOut = new PropertyAnimator(duration, view,
- "alpha", view.getAlpha(), 0.0f);
- fadeOut.start();
} else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) {
// Make sure this view that is "waiting in the wings" is invisible
view.setAlpha(0.0f);
@@ -233,7 +248,6 @@
view.setClipChildren(false);
view.setClipToPadding(false);
}
-
mFirstLayoutHappened = true;
}
}
@@ -258,16 +272,10 @@
Log.d(TAG, "Error: No data for our primary pointer.");
return false;
}
-
float newY = ev.getY(pointerIndex);
float deltaY = newY - mInitialY;
- if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
- mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
- mGestureComplete = false;
- cancelLongPress();
- requestDisallowInterceptTouchEvent(true);
- }
+ beginGestureIfNeeded(deltaY);
break;
}
case MotionEvent.ACTION_POINTER_UP: {
@@ -278,13 +286,33 @@
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER;
mSwipeGestureType = GESTURE_NONE;
- mGestureComplete = true;
}
}
return mSwipeGestureType != GESTURE_NONE;
}
+ private void beginGestureIfNeeded(float deltaY) {
+ if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
+ mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
+ cancelLongPress();
+ requestDisallowInterceptTouchEvent(true);
+
+ int activeIndex = mSwipeGestureType == GESTURE_SLIDE_DOWN ? mNumActiveViews - 1
+ : mNumActiveViews - 2;
+
+ View v = getViewAtRelativeIndex(activeIndex);
+ if (v != null) {
+ mHighlight.setImageBitmap(createOutline(v));
+ mHighlight.bringToFront();
+ v.bringToFront();
+ mStackSlider.setView(v);
+ if (mSwipeGestureType == GESTURE_SLIDE_DOWN)
+ v.setVisibility(VISIBLE);
+ }
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
@@ -296,8 +324,9 @@
}
float newY = ev.getY(pointerIndex);
+ float newX = ev.getX(pointerIndex);
float deltaY = newY - mInitialY;
-
+ float deltaX = newX - mInitialX;
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
@@ -305,48 +334,21 @@
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
- if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
- mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
- mGestureComplete = false;
- cancelLongPress();
- requestDisallowInterceptTouchEvent(true);
+ beginGestureIfNeeded(deltaY);
+
+ float rx = 0.3f*deltaX/(mViewHeight*1.0f);
+ if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
+ float r = (deltaY-mTouchSlop*1.0f)/mViewHeight*1.0f;
+ mStackSlider.setYProgress(1 - r);
+ mStackSlider.setXProgress(rx);
+ return true;
+ } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
+ float r = -(deltaY + mTouchSlop*1.0f)/mViewHeight*1.0f;
+ mStackSlider.setYProgress(r);
+ mStackSlider.setXProgress(rx);
+ return true;
}
- if (!mGestureComplete) {
- if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
- View v = getViewAtRelativeIndex(mNumActiveViews - 1);
- if (v != null) {
- // This view is present but hidden, make sure it's visible
- // if they pull down
- v.setVisibility(VISIBLE);
-
- float r = (deltaY-mTouchSlop)*1.0f / (mSwipeThreshold);
- mYOffset = Math.min(-mViewHeight + (int) Math.round(
- r*mSwipeThreshold) - mTouchSlop, 0);
- LayoutParams lp = (LayoutParams) v.getLayoutParams();
- lp.setVerticalOffset(mYOffset);
-
- float alpha = Math.max(0.0f, 1.0f - (1.0f*mYOffset/-mViewHeight));
- alpha = Math.min(1.0f, alpha);
- v.setAlpha(alpha);
- }
- return true;
- } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
- View v = getViewAtRelativeIndex(mNumActiveViews - 2);
-
- if (v != null) {
- float r = -(deltaY*1.0f + mTouchSlop) / (mSwipeThreshold);
- mYOffset = Math.min((int) Math.round(r*-mSwipeThreshold), 0);
- LayoutParams lp = (LayoutParams) v.getLayoutParams();
- lp.setVerticalOffset(mYOffset);
-
- float alpha = Math.max(0.0f, 1.0f - (1.0f*mYOffset/-mViewHeight));
- alpha = Math.min(1.0f, alpha);
- v.setAlpha(alpha);
- }
- return true;
- }
- }
break;
}
case MotionEvent.ACTION_UP: {
@@ -359,9 +361,7 @@
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER;
- mGestureComplete = true;
mSwipeGestureType = GESTURE_NONE;
- mYOffset = 0;
break;
}
}
@@ -427,56 +427,108 @@
mVelocityTracker = null;
}
- if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN &&
- !mGestureComplete) {
+ if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN) {
// Swipe threshold exceeded, swipe down
showNext();
- } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP &&
- !mGestureComplete) {
+ mHighlight.bringToFront();
+ } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP) {
// Swipe threshold exceeded, swipe up
showPrevious();
- } else if (mSwipeGestureType == GESTURE_SLIDE_UP && !mGestureComplete) {
+ mHighlight.bringToFront();
+ } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
// Didn't swipe up far enough, snap back down
- View v = getViewAtRelativeIndex(mNumActiveViews - 2);
- if (v != null) {
- // Compute the animation duration based on how far they pulled it up
- LayoutParams lp = (LayoutParams) v.getLayoutParams();
- int duration = (int) Math.round(
- lp.verticalOffset*1.0f/-mViewHeight*DEFAULT_ANIMATION_DURATION);
- duration = Math.max(MINIMUM_ANIMATION_DURATION, duration);
+ int duration = (int) Math.round(mStackSlider.getYProgress()*DEFAULT_ANIMATION_DURATION);
- // Animate back down
- PropertyAnimator slideDown = new PropertyAnimator(duration, lp,
- "verticalOffset", lp.verticalOffset, 0);
- slideDown.start();
- PropertyAnimator fadeIn = new PropertyAnimator(duration, v,
- "alpha",v.getAlpha(), 1.0f);
- fadeIn.start();
- }
- } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN && !mGestureComplete) {
+ PropertyAnimator snapBackY = new PropertyAnimator(duration, mStackSlider,
+ "YProgress", mStackSlider.getYProgress(), 0);
+ snapBackY.start();
+ PropertyAnimator snapBackX = new PropertyAnimator(duration, mStackSlider,
+ "XProgress", mStackSlider.getXProgress(), 0);
+ snapBackX.start();
+ } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
// Didn't swipe down far enough, snap back up
- View v = getViewAtRelativeIndex(mNumActiveViews - 1);
- if (v != null) {
- // Compute the animation duration based on how far they pulled it down
- LayoutParams lp = (LayoutParams) v.getLayoutParams();
- int duration = (int) Math.round(
- (1 - lp.verticalOffset*1.0f/-mViewHeight)*DEFAULT_ANIMATION_DURATION);
- duration = Math.max(MINIMUM_ANIMATION_DURATION, duration);
-
- // Animate back up
- PropertyAnimator slideUp = new PropertyAnimator(duration, lp,
- "verticalOffset", lp.verticalOffset, -mViewHeight);
- slideUp.start();
- PropertyAnimator fadeOut = new PropertyAnimator(duration, v,
- "alpha",v.getAlpha(), 0.0f);
- fadeOut.start();
- }
+ int duration = (int) Math.round((1 -
+ mStackSlider.getYProgress())*DEFAULT_ANIMATION_DURATION);
+ PropertyAnimator snapBackY = new PropertyAnimator(duration, mStackSlider,
+ "YProgress", mStackSlider.getYProgress(), 1);
+ snapBackY.start();
+ PropertyAnimator snapBackX = new PropertyAnimator(duration, mStackSlider,
+ "XProgress", mStackSlider.getXProgress(), 0);
+ snapBackX.start();
}
mActivePointerId = INVALID_POINTER;
- mGestureComplete = true;
mSwipeGestureType = GESTURE_NONE;
- mYOffset = 0;
+ }
+
+ private class StackSlider {
+ View mView;
+ float mYProgress;
+ float mXProgress;
+
+ private float cubic(float r) {
+ return (float) (Math.pow(2*r-1, 3) + 1)/2.0f;
+ }
+
+ private float highlightAlphaInterpolator(float r) {
+ float pivot = 0.4f;
+ if (r < pivot) {
+ return 0.85f*cubic(r/pivot);
+ } else {
+ return 0.85f*cubic(1 - (r-pivot)/(1-pivot));
+ }
+ }
+
+ private float viewAlphaInterpolator(float r) {
+ float pivot = 0.3f;
+ if (r > pivot) {
+ return (r - pivot)/(1 - pivot);
+ } else {
+ return 0;
+ }
+ }
+
+ void setView(View v) {
+ mView = v;
+ }
+
+ public void setYProgress(float r) {
+ // enforce r between 0 and 1
+ r = Math.min(1.0f, r);
+ r = Math.max(0, r);
+
+ mYProgress = r;
+
+ final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
+ final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
+
+ viewLp.setVerticalOffset((int) Math.round(-r*mViewHeight));
+ highlightLp.setVerticalOffset((int) Math.round(-r*mViewHeight));
+ mHighlight.setAlpha(highlightAlphaInterpolator(r));
+ mView.setAlpha(viewAlphaInterpolator(1-r));
+ }
+
+ public void setXProgress(float r) {
+ // enforce r between 0 and 1
+ r = Math.min(1.0f, r);
+ r = Math.max(-1.0f, r);
+
+ mXProgress = r;
+
+ final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
+ final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
+
+ viewLp.setHorizontalOffset((int) Math.round(r*mViewHeight));
+ highlightLp.setHorizontalOffset((int) Math.round(r*mViewHeight));
+ }
+
+ float getYProgress() {
+ return mYProgress;
+ }
+
+ float getXProgress() {
+ return mXProgress;
+ }
}
@Override
@@ -484,4 +536,49 @@
super.onRemoteAdapterConnected();
setDisplayedChild(mIndex);
}
+
+ private static final Paint sHolographicPaint = new Paint();
+ private static final Paint sErasePaint = new Paint();
+ private static boolean sPaintsInitialized = false;
+ private static final float STROKE_WIDTH = 3.0f;
+
+ static void initializePaints(Context context) {
+ sHolographicPaint.setColor(0xff6699ff);
+ sHolographicPaint.setFilterBitmap(true);
+ sErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ sErasePaint.setFilterBitmap(true);
+ sPaintsInitialized = true;
+ }
+
+ static Bitmap createOutline(View v) {
+ Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+
+ canvas.concat(v.getMatrix());
+ v.draw(canvas);
+
+ Bitmap outlineBitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas outlineCanvas = new Canvas(outlineBitmap);
+ drawOutline(outlineCanvas, v.getMeasuredWidth(), v.getMeasuredHeight(), bitmap);
+ bitmap.recycle();
+ return outlineBitmap;
+ }
+
+ static void drawOutline(Canvas dest, int destWidth, int destHeight, Bitmap src) {
+ dest.drawColor(0, PorterDuff.Mode.CLEAR);
+
+ Bitmap mask = src.extractAlpha();
+ Matrix id = new Matrix();
+
+ Matrix m = new Matrix();
+ float xScale = STROKE_WIDTH*2/(src.getWidth());
+ float yScale = STROKE_WIDTH*2/(src.getHeight());
+ m.preScale(1+xScale, 1+yScale, src.getWidth()/2, src.getHeight()/2);
+ dest.drawBitmap(mask, m, sHolographicPaint);
+
+ dest.drawBitmap(src, id, sErasePaint);
+ mask.recycle();
+ }
}