Merge change 26390 into eclair

* changes:
  Bring the dimples back, and make the "spin around" animation have a fling feeling.
This commit is contained in:
Android (Google) Code Review
2009-09-22 17:01:13 -04:00

View File

@@ -25,8 +25,9 @@ import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.SoundEffectConstants; import android.view.VelocityTracker;
import android.view.animation.AccelerateInterpolator; import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import static android.view.animation.AnimationUtils.currentAnimationTimeMillis; import static android.view.animation.AnimationUtils.currentAnimationTimeMillis;
import com.android.internal.R; import com.android.internal.R;
@@ -60,16 +61,18 @@ public class RotarySelector extends View {
private int mLeftHandleX; private int mLeftHandleX;
private int mRightHandleX; private int mRightHandleX;
// current offset of user's dragging // current offset of rotary widget along the x axis
private int mTouchDragOffset = 0; private int mRotaryOffsetX = 0;
// state of the animation used to bring the handle back to its start position when // state of the animation used to bring the handle back to its start position when
// the user lets go before triggering an action // the user lets go before triggering an action
private boolean mAnimating = false; private boolean mAnimating = false;
private long mAnimationStartTime; // set to the end point of the animatino private long mAnimationStartTime;
private long mAnimationDuration; private long mAnimationDuration;
private int mAnimatingDeltaXStart; // the animation will interpolate from this delta down to zero private int mAnimatingDeltaXStart; // the animation will interpolate from this delta to zero
private AccelerateInterpolator mInterpolator; private int mAnimatingDeltaXEnd;
private DecelerateInterpolator mInterpolator;
/** /**
* If the user is currently dragging something. * If the user is currently dragging something.
@@ -87,8 +90,8 @@ public class RotarySelector extends View {
// Vibration (haptic feedback) // Vibration (haptic feedback)
private Vibrator mVibrator; private Vibrator mVibrator;
private static final long VIBRATE_SHORT = 60; // msec private static final long VIBRATE_SHORT = 30; // msec
private static final long VIBRATE_LONG = 100; // msec private static final long VIBRATE_LONG = 60; // msec
/** /**
* The drawable for the arrows need to be scrunched this many dips towards the rotary bg below * The drawable for the arrows need to be scrunched this many dips towards the rotary bg below
@@ -114,11 +117,27 @@ public class RotarySelector extends View {
static final int SNAP_BACK_ANIMATION_DURATION_MILLIS = 300; static final int SNAP_BACK_ANIMATION_DURATION_MILLIS = 300;
static final int SPIN_ANIMATION_DURATION_MILLIS = 800; static final int SPIN_ANIMATION_DURATION_MILLIS = 800;
private static final boolean DRAW_CENTER_DIMPLE = false; private static final boolean DRAW_CENTER_DIMPLE = true;
private int mEdgeTriggerThresh; private int mEdgeTriggerThresh;
private int mDimpleWidth; private int mDimpleWidth;
private int mBackgroundWidth; private int mBackgroundWidth;
private int mBackgroundHeight; private int mBackgroundHeight;
private final int mOuterRadius;
private final int mInnerRadius;
private int mDimpleSpacing;
private VelocityTracker mVelocityTracker;
private int mMinimumVelocity;
private int mMaximumVelocity;
/**
* The number of dimples we are flinging when we do the "spin" animation. Used to know when to
* wrap the icons back around so they "rotate back" onto the screen.
* @see #updateAnimation()
*/
private int mDimplesOfFling = 0;
public RotarySelector(Context context) { public RotarySelector(Context context) {
this(context, null); this(context, null);
@@ -152,7 +171,7 @@ public class RotarySelector extends View {
mArrowLongLeft.setBounds(0, 0, arrowW, arrowH); mArrowLongLeft.setBounds(0, 0, arrowW, arrowH);
mArrowLongRight.setBounds(0, 0, arrowW, arrowH); mArrowLongRight.setBounds(0, 0, arrowW, arrowH);
mInterpolator = new AccelerateInterpolator(); mInterpolator = new DecelerateInterpolator(1f);
mEdgeTriggerThresh = (int) (mDensity * EDGE_TRIGGER_DIP); mEdgeTriggerThresh = (int) (mDensity * EDGE_TRIGGER_DIP);
@@ -160,6 +179,23 @@ public class RotarySelector extends View {
mBackgroundWidth = mBackground.getIntrinsicWidth(); mBackgroundWidth = mBackground.getIntrinsicWidth();
mBackgroundHeight = mBackground.getIntrinsicHeight(); mBackgroundHeight = mBackground.getIntrinsicHeight();
mOuterRadius = (int) (mDensity * OUTER_ROTARY_RADIUS_DIP);
mInnerRadius = (int) ((OUTER_ROTARY_RADIUS_DIP - ROTARY_STROKE_WIDTH_DIP) * mDensity);
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity() * 2;
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mLeftHandleX = (int) (EDGE_PADDING_DIP * mDensity) + mDimpleWidth / 2;
mRightHandleX =
getWidth() - (int) (EDGE_PADDING_DIP * mDensity) - mDimpleWidth / 2;
mDimpleSpacing = (getWidth() / 2) - mLeftHandleX;
} }
/** /**
@@ -229,43 +265,21 @@ public class RotarySelector extends View {
setMeasuredDimension(width, backgroundH + arrowH - arrowScrunch); setMeasuredDimension(width, backgroundH + arrowH - arrowScrunch);
} }
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mLeftHandleX = (int) (EDGE_PADDING_DIP * mDensity) + mDimpleWidth / 2;
mRightHandleX =
getWidth() - (int) (EDGE_PADDING_DIP * mDensity) - mDimpleWidth / 2;
}
// private Paint mPaint = new Paint(); // private Paint mPaint = new Paint();
@Override @Override
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
if (DBG) { if (DBG) {
log(String.format("onDraw: mAnimating=%s, mTouchDragOffset=%d, mGrabbedState=%d", log(String.format("onDraw: mAnimating=%s, mRotaryOffsetX=%d, mGrabbedState=%d",
mAnimating, mTouchDragOffset, mGrabbedState)); mAnimating, mRotaryOffsetX, mGrabbedState));
} }
final int height = getHeight(); final int height = getHeight();
// update animating state before we draw anything // update animating state before we draw anything
if (mAnimating) { if (mAnimating) {
final long millisSoFar = currentAnimationTimeMillis() - mAnimationStartTime; updateAnimation();
final long millisLeft = mAnimationDuration - millisSoFar;
if (DBG) log("millisleft for animating: " + millisLeft);
if (millisLeft <= 0) {
reset();
} else {
// we always use the snap back duration as the denominator for interpolation
// to get a consistent velocity (bascially this makes us happy for the snap back
// and the spin around one).
final long denom = SNAP_BACK_ANIMATION_DURATION_MILLIS; // mAnimationDuration
float interpolation = mInterpolator.getInterpolation(
(float) millisLeft / denom);
mTouchDragOffset = (int) (mAnimatingDeltaXStart * interpolation);
}
} }
// Background: // Background:
@@ -302,16 +316,13 @@ public class RotarySelector extends View {
// float or = OUTER_ROTARY_RADIUS_DIP * mDensity; // float or = OUTER_ROTARY_RADIUS_DIP * mDensity;
// canvas.drawCircle(getWidth() / 2, or + mBackground.getBounds().top, or, mPaint); // canvas.drawCircle(getWidth() / 2, or + mBackground.getBounds().top, or, mPaint);
final int outerRadius = (int) (mDensity * OUTER_ROTARY_RADIUS_DIP);
final int innerRadius =
(int) ((OUTER_ROTARY_RADIUS_DIP - ROTARY_STROKE_WIDTH_DIP) * mDensity);
final int bgTop = mBackground.getBounds().top; final int bgTop = mBackground.getBounds().top;
{ {
final int xOffset = mLeftHandleX + mTouchDragOffset; final int xOffset = mLeftHandleX + mRotaryOffsetX;
final int drawableY = getYOnArc( final int drawableY = getYOnArc(
mBackground, mBackground,
innerRadius, mInnerRadius,
outerRadius, mOuterRadius,
xOffset); xOffset);
drawCentered(mDimple, canvas, xOffset, drawableY + bgTop); drawCentered(mDimple, canvas, xOffset, drawableY + bgTop);
@@ -321,22 +332,22 @@ public class RotarySelector extends View {
} }
if (DRAW_CENTER_DIMPLE) { if (DRAW_CENTER_DIMPLE) {
final int xOffset = getWidth() / 2 + mTouchDragOffset; final int xOffset = getWidth() / 2 + mRotaryOffsetX;
final int drawableY = getYOnArc( final int drawableY = getYOnArc(
mBackground, mBackground,
innerRadius, mInnerRadius,
outerRadius, mOuterRadius,
xOffset); xOffset);
drawCentered(mDimple, canvas, xOffset, drawableY + bgTop); drawCentered(mDimple, canvas, xOffset, drawableY + bgTop);
} }
{ {
final int xOffset = mRightHandleX + mTouchDragOffset; final int xOffset = mRightHandleX + mRotaryOffsetX;
final int drawableY = getYOnArc( final int drawableY = getYOnArc(
mBackground, mBackground,
innerRadius, mInnerRadius,
outerRadius, mOuterRadius,
xOffset); xOffset);
drawCentered(mDimple, canvas, xOffset, drawableY + bgTop); drawCentered(mDimple, canvas, xOffset, drawableY + bgTop);
@@ -345,7 +356,33 @@ public class RotarySelector extends View {
} }
} }
if (mAnimating) invalidate(); // draw extra left hand dimples
int dimpleLeft = mRotaryOffsetX + mLeftHandleX - mDimpleSpacing;
final int halfdimple = mDimpleWidth / 2;
while (dimpleLeft > -halfdimple) {
final int drawableY = getYOnArc(
mBackground,
mInnerRadius,
mOuterRadius,
dimpleLeft);
drawCentered(mDimple, canvas, dimpleLeft, drawableY + bgTop);
dimpleLeft -= mDimpleSpacing;
}
// draw extra right hand dimples
int dimpleRight = mRotaryOffsetX + mRightHandleX + mDimpleSpacing;
final int rightThresh = mRight + halfdimple;
while (dimpleRight < rightThresh) {
final int drawableY = getYOnArc(
mBackground,
mInnerRadius,
mOuterRadius,
dimpleRight);
drawCentered(mDimple, canvas, dimpleRight, drawableY + bgTop);
dimpleRight += mDimpleSpacing;
}
} }
/** /**
@@ -395,6 +432,11 @@ public class RotarySelector extends View {
if (mAnimating) { if (mAnimating) {
return true; return true;
} }
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
final int eventX = (int) event.getX(); final int eventX = (int) event.getX();
final int hitWindow = mDimpleWidth; final int hitWindow = mDimpleWidth;
@@ -409,12 +451,12 @@ public class RotarySelector extends View {
invalidate(); invalidate();
} }
if (eventX < mLeftHandleX + hitWindow) { if (eventX < mLeftHandleX + hitWindow) {
mTouchDragOffset = eventX - mLeftHandleX; mRotaryOffsetX = eventX - mLeftHandleX;
mGrabbedState = LEFT_HANDLE_GRABBED; mGrabbedState = LEFT_HANDLE_GRABBED;
invalidate(); invalidate();
vibrate(VIBRATE_SHORT); vibrate(VIBRATE_SHORT);
} else if (eventX > mRightHandleX - hitWindow) { } else if (eventX > mRightHandleX - hitWindow) {
mTouchDragOffset = eventX - mRightHandleX; mRotaryOffsetX = eventX - mRightHandleX;
mGrabbedState = RIGHT_HANDLE_GRABBED; mGrabbedState = RIGHT_HANDLE_GRABBED;
invalidate(); invalidate();
vibrate(VIBRATE_SHORT); vibrate(VIBRATE_SHORT);
@@ -424,35 +466,38 @@ public class RotarySelector extends View {
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
if (DBG) log("touch-move"); if (DBG) log("touch-move");
if (mGrabbedState == LEFT_HANDLE_GRABBED) { if (mGrabbedState == LEFT_HANDLE_GRABBED) {
mTouchDragOffset = eventX - mLeftHandleX; mRotaryOffsetX = eventX - mLeftHandleX;
invalidate(); invalidate();
if (eventX >= getRight() - mEdgeTriggerThresh && !mTriggered) { if (eventX >= getRight() - mEdgeTriggerThresh && !mTriggered) {
mTriggered = true; mTriggered = true;
dispatchTriggerEvent(OnDialTriggerListener.LEFT_HANDLE); dispatchTriggerEvent(OnDialTriggerListener.LEFT_HANDLE);
// set up "spin around animation" final VelocityTracker velocityTracker = mVelocityTracker;
mAnimating = true; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
mAnimationStartTime = currentAnimationTimeMillis(); final int velocity = Math.max(mMinimumVelocity, (int) velocityTracker.getXVelocity());
mAnimationDuration = SPIN_ANIMATION_DURATION_MILLIS; mDimplesOfFling = Math.max(
mAnimatingDeltaXStart = -mBackgroundWidth*3; 8,
mTouchDragOffset = 0; Math.abs(velocity / mDimpleSpacing));
mGrabbedState = NOTHING_GRABBED; startAnimationWithVelocity(
invalidate(); eventX - mLeftHandleX,
mDimplesOfFling * mDimpleSpacing,
velocity);
} }
} else if (mGrabbedState == RIGHT_HANDLE_GRABBED) { } else if (mGrabbedState == RIGHT_HANDLE_GRABBED) {
mTouchDragOffset = eventX - mRightHandleX; mRotaryOffsetX = eventX - mRightHandleX;
invalidate(); invalidate();
if (eventX <= mEdgeTriggerThresh && !mTriggered) { if (eventX <= mEdgeTriggerThresh && !mTriggered) {
mTriggered = true; mTriggered = true;
dispatchTriggerEvent(OnDialTriggerListener.RIGHT_HANDLE); dispatchTriggerEvent(OnDialTriggerListener.RIGHT_HANDLE);
// set up "spin around animation" final VelocityTracker velocityTracker = mVelocityTracker;
mAnimating = true; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
mAnimationStartTime = currentAnimationTimeMillis(); final int velocity = Math.min(-mMinimumVelocity, (int) velocityTracker.getXVelocity());
mAnimationDuration = SPIN_ANIMATION_DURATION_MILLIS; mDimplesOfFling = Math.max(
mAnimatingDeltaXStart = mBackgroundWidth*3; 8,
mTouchDragOffset = 0; Math.abs(velocity / mDimpleSpacing));
mGrabbedState = NOTHING_GRABBED; startAnimationWithVelocity(
invalidate(); eventX - mRightHandleX,
-(mDimplesOfFling * mDimpleSpacing),
velocity);
} }
} }
break; break;
@@ -462,35 +507,84 @@ public class RotarySelector extends View {
if (mGrabbedState == LEFT_HANDLE_GRABBED if (mGrabbedState == LEFT_HANDLE_GRABBED
&& Math.abs(eventX - mLeftHandleX) > 5) { && Math.abs(eventX - mLeftHandleX) > 5) {
// set up "snap back" animation // set up "snap back" animation
mAnimating = true; startAnimation(eventX - mLeftHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS);
mAnimationStartTime = currentAnimationTimeMillis();
mAnimationDuration = SNAP_BACK_ANIMATION_DURATION_MILLIS;
mAnimatingDeltaXStart = eventX - mLeftHandleX;
} else if (mGrabbedState == RIGHT_HANDLE_GRABBED } else if (mGrabbedState == RIGHT_HANDLE_GRABBED
&& Math.abs(eventX - mRightHandleX) > 5) { && Math.abs(eventX - mRightHandleX) > 5) {
// set up "snap back" animation // set up "snap back" animation
mAnimating = true; startAnimation(eventX - mRightHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS);
mAnimationStartTime = currentAnimationTimeMillis();
mAnimationDuration = SNAP_BACK_ANIMATION_DURATION_MILLIS;
mAnimatingDeltaXStart = eventX - mRightHandleX;
} }
mRotaryOffsetX = 0;
mTouchDragOffset = 0;
mGrabbedState = NOTHING_GRABBED; mGrabbedState = NOTHING_GRABBED;
invalidate(); invalidate();
if (mVelocityTracker != null) {
mVelocityTracker.recycle(); // wishin' we had generational GC
mVelocityTracker = null;
}
break; break;
case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_CANCEL:
if (DBG) log("touch-cancel"); if (DBG) log("touch-cancel");
reset(); reset();
invalidate(); invalidate();
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break; break;
} }
return true; return true;
} }
private void startAnimation(int startX, int endX, int duration) {
mAnimating = true;
mAnimationStartTime = currentAnimationTimeMillis();
mAnimationDuration = duration;
mAnimatingDeltaXStart = startX;
mAnimatingDeltaXEnd = endX;
mGrabbedState = NOTHING_GRABBED;
mDimplesOfFling = 0;
invalidate();
}
private void startAnimationWithVelocity(int startX, int endX, int pixelsPerSecond) {
mAnimating = true;
mAnimationStartTime = currentAnimationTimeMillis();
mAnimationDuration = 1000 * (endX - startX) / pixelsPerSecond;
mAnimatingDeltaXStart = startX;
mAnimatingDeltaXEnd = endX;
mGrabbedState = NOTHING_GRABBED;
invalidate();
}
private void updateAnimation() {
final long millisSoFar = currentAnimationTimeMillis() - mAnimationStartTime;
final long millisLeft = mAnimationDuration - millisSoFar;
final int totalDeltaX = mAnimatingDeltaXStart - mAnimatingDeltaXEnd;
if (DBG) log("millisleft for animating: " + millisLeft);
if (millisLeft <= 0) {
reset();
return;
}
// from 0 to 1 as animation progresses
float interpolation =
mInterpolator.getInterpolation((float) millisSoFar / mAnimationDuration);
final int dx = (int) (totalDeltaX * (1 - interpolation));
mRotaryOffsetX = mAnimatingDeltaXEnd + dx;
if (mDimplesOfFling > 0) {
if (mRotaryOffsetX < 4 * mDimpleSpacing) {
// wrap around on fling left
mRotaryOffsetX += (4 + mDimplesOfFling - 4) * mDimpleSpacing;
} else if (mRotaryOffsetX > 4 * mDimpleSpacing) {
// wrap around on fling right
mRotaryOffsetX -= (4 + mDimplesOfFling - 4) * mDimpleSpacing;
}
}
invalidate();
}
private void reset() { private void reset() {
mAnimating = false; mAnimating = false;
mTouchDragOffset = 0; mRotaryOffsetX = 0;
mDimplesOfFling = 0;
mGrabbedState = NOTHING_GRABBED; mGrabbedState = NOTHING_GRABBED;
mTriggered = false; mTriggered = false;
} }
@@ -500,7 +594,8 @@ public class RotarySelector extends View {
*/ */
private synchronized void vibrate(long duration) { private synchronized void vibrate(long duration) {
if (mVibrator == null) { if (mVibrator == null) {
mVibrator = (android.os.Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); mVibrator = (android.os.Vibrator)
getContext().getSystemService(Context.VIBRATOR_SERVICE);
} }
mVibrator.vibrate(duration); mVibrator.vibrate(duration);
} }