diff options
Diffstat (limited to 'core/java/android/widget/ZoomRing.java')
-rw-r--r-- | core/java/android/widget/ZoomRing.java | 271 |
1 files changed, 181 insertions, 90 deletions
diff --git a/core/java/android/widget/ZoomRing.java b/core/java/android/widget/ZoomRing.java index 20d6056..be3b1fb 100644 --- a/core/java/android/widget/ZoomRing.java +++ b/core/java/android/widget/ZoomRing.java @@ -6,10 +6,9 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.drawable.Drawable; -import android.os.Handler; +import android.graphics.drawable.RotateDrawable; import android.util.AttributeSet; -import android.util.Log; -import android.view.KeyEvent; +import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -18,17 +17,20 @@ import android.view.ViewConfiguration; * @hide */ public class ZoomRing extends View { - + // TODO: move to ViewConfiguration? - private static final int DOUBLE_TAP_DISMISS_TIMEOUT = ViewConfiguration.getJumpTapTimeout(); + static final int DOUBLE_TAP_DISMISS_TIMEOUT = ViewConfiguration.getJumpTapTimeout(); // TODO: get from theme private static final int DISABLED_ALPHA = 160; - + private static final String TAG = "ZoomRing"; + // TODO: Temporary until the trail is done + private static final boolean DRAW_TRAIL = false; + // TODO: xml - private static final int THUMB_DISTANCE = 63; - + private static final int THUMB_DISTANCE = 63; + /** To avoid floating point calculations, we multiply radians by this value. */ public static final int RADIAN_INT_MULTIPLIER = 100000000; /** PI using our multiplier. */ @@ -36,68 +38,81 @@ public class ZoomRing extends View { /** PI/2 using our multiplier. */ private static final int HALF_PI_INT_MULTIPLIED = PI_INT_MULTIPLIED / 2; + private int mZeroAngle = HALF_PI_INT_MULTIPLIED * 3; + private static final int THUMB_GRAB_SLOP = PI_INT_MULTIPLIED / 4; - + /** The cached X of our center. */ private int mCenterX; - /** The cached Y of our center. */ + /** The cached Y of our center. */ private int mCenterY; /** The angle of the thumb (in int radians) */ private int mThumbAngle; private boolean mIsThumbAngleValid; - private int mThumbCenterX; - private int mThumbCenterY; private int mThumbHalfWidth; private int mThumbHalfHeight; - - private int mCallbackThreshold = Integer.MAX_VALUE; - - /** The accumulated amount of drag for the thumb (in int radians). */ - private int mAcculumalatedThumbDrag = 0; - + /** The inner radius of the track. */ private int mBoundInnerRadiusSquared = 0; /** The outer radius of the track. */ private int mBoundOuterRadiusSquared = Integer.MAX_VALUE; - + private int mPreviousWidgetDragX; private int mPreviousWidgetDragY; - + private boolean mDrawThumb = true; private Drawable mThumbDrawable; - + private static final int MODE_IDLE = 0; private static final int MODE_DRAG_THUMB = 1; + /** + * User has his finger down, but we are waiting for him to pass the touch + * slop before going into the #MODE_MOVE_ZOOM_RING. This is a good time to + * show the movable hint. + */ + private static final int MODE_WAITING_FOR_MOVE_ZOOM_RING = 4; private static final int MODE_MOVE_ZOOM_RING = 2; private static final int MODE_TAP_DRAG = 3; private int mMode; - private long mPreviousTapTime; - - private Handler mHandler = new Handler(); - + private long mPreviousDownTime; + private int mPreviousDownX; + private int mPreviousDownY; + private Disabler mDisabler = new Disabler(); - + private OnZoomRingCallback mCallback; - + private int mPreviousCallbackAngle; + private int mCallbackThreshold = Integer.MAX_VALUE; + private boolean mResetThumbAutomatically = true; private int mThumbDragStartAngle; - + private final int mTouchSlop; + private Drawable mTrail; + private double mAcculumalatedTrailAngle; + public ZoomRing(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - // TODO get drawable from style instead + + ViewConfiguration viewConfiguration = ViewConfiguration.get(context); + mTouchSlop = viewConfiguration.getScaledTouchSlop(); + + // TODO get drawables from style instead Resources res = context.getResources(); mThumbDrawable = res.getDrawable(R.drawable.zoom_ring_thumb); - + if (DRAW_TRAIL) { + mTrail = res.getDrawable(R.drawable.zoom_ring_trail).mutate(); + } + // TODO: add padding to drawable setBackgroundResource(R.drawable.zoom_ring_track); // TODO get from style setBounds(30, Integer.MAX_VALUE); - + mThumbHalfHeight = mThumbDrawable.getIntrinsicHeight() / 2; mThumbHalfWidth = mThumbDrawable.getIntrinsicWidth() / 2; - + mCallbackThreshold = PI_INT_MULTIPLIED / 6; } @@ -108,7 +123,7 @@ public class ZoomRing extends View { public ZoomRing(Context context) { this(context, null); } - + public void setCallback(OnZoomRingCallback callback) { mCallback = callback; } @@ -132,26 +147,49 @@ public class ZoomRing extends View { mBoundOuterRadiusSquared = Integer.MAX_VALUE; } } - + public void setThumbAngle(int angle) { mThumbAngle = angle; - mThumbCenterX = (int) (Math.cos(1f * angle / RADIAN_INT_MULTIPLIER) * THUMB_DISTANCE) - + mCenterX; - mThumbCenterY = (int) (Math.sin(1f * angle / RADIAN_INT_MULTIPLIER) * THUMB_DISTANCE) - * -1 + mCenterY; + int unoffsetAngle = angle + mZeroAngle; + int thumbCenterX = (int) (Math.cos(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) * + THUMB_DISTANCE) + mCenterX; + int thumbCenterY = (int) (Math.sin(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) * + THUMB_DISTANCE) * -1 + mCenterY; + + mThumbDrawable.setBounds(thumbCenterX - mThumbHalfWidth, + thumbCenterY - mThumbHalfHeight, + thumbCenterX + mThumbHalfWidth, + thumbCenterY + mThumbHalfHeight); + + if (DRAW_TRAIL) { + double degrees; + degrees = Math.min(359.0, Math.abs(mAcculumalatedTrailAngle)); + int level = (int) (10000.0 * degrees / 360.0); + + mTrail.setLevel((int) (10000.0 * + (-Math.toDegrees(angle / (double) RADIAN_INT_MULTIPLIER) - + degrees + 90) / 360.0)); + ((RotateDrawable) mTrail).getDrawable().setLevel(level); + } + invalidate(); } - + + public void resetThumbAngle(int angle) { + mPreviousCallbackAngle = angle; + setThumbAngle(angle); + } + public void resetThumbAngle() { if (mResetThumbAutomatically) { - setThumbAngle(HALF_PI_INT_MULTIPLIED); + resetThumbAngle(0); } } - + public void setResetThumbAutomatically(boolean resetThumbAutomatically) { mResetThumbAutomatically = resetThumbAutomatically; } - + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), @@ -162,7 +200,7 @@ public class ZoomRing extends View { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - + // Cache the center point mCenterX = (right - left) / 2; mCenterY = (bottom - top) / 2; @@ -172,8 +210,12 @@ public class ZoomRing extends View { if (mThumbAngle == Integer.MIN_VALUE) { resetThumbAngle(); } + + if (DRAW_TRAIL) { + mTrail.setBounds(0, 0, right - left, bottom - top); + } } - + @Override public boolean onTouchEvent(MotionEvent event) { return handleTouch(event.getAction(), event.getEventTime(), @@ -184,61 +226,66 @@ public class ZoomRing extends View { private void resetState() { mMode = MODE_IDLE; mPreviousWidgetDragX = mPreviousWidgetDragY = Integer.MIN_VALUE; - mAcculumalatedThumbDrag = 0; + mAcculumalatedTrailAngle = 0.0; mIsThumbAngleValid = false; } - + public void setTapDragMode(boolean tapDragMode, int x, int y) { resetState(); mMode = tapDragMode ? MODE_TAP_DRAG : MODE_IDLE; mIsThumbAngleValid = false; - + if (tapDragMode && mCallback != null) { onThumbDragStarted(getAngle(x - mCenterX, y - mCenterY)); } } - + public boolean handleTouch(int action, long time, int x, int y, int rawX, int rawY) { switch (action) { - + case MotionEvent.ACTION_DOWN: - if (mPreviousTapTime + DOUBLE_TAP_DISMISS_TIMEOUT >= time) { + if (mPreviousDownTime + DOUBLE_TAP_DISMISS_TIMEOUT >= time) { if (mCallback != null) { mCallback.onZoomRingDismissed(); } } else { - mPreviousTapTime = time; + mPreviousDownTime = time; + mPreviousDownX = x; + mPreviousDownY = y; } resetState(); return true; - + case MotionEvent.ACTION_MOVE: // Fall through to code below switch break; - + case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (mCallback != null) { - if (mMode == MODE_MOVE_ZOOM_RING) { - mCallback.onZoomRingMovingStopped(); + if (mMode == MODE_MOVE_ZOOM_RING || mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { + mCallback.onZoomRingSetMovableHintVisible(false); + if (mMode == MODE_MOVE_ZOOM_RING) { + mCallback.onZoomRingMovingStopped(); + } } else if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) { onThumbDragStopped(getAngle(x - mCenterX, y - mCenterY)); } } mDisabler.setEnabling(true); return true; - + default: return false; } - + // local{X,Y} will be where the center of the widget is (0,0) int localX = x - mCenterX; int localY = y - mCenterY; boolean isTouchingThumb = true; boolean isInBounds = true; int touchAngle = getAngle(localX, localY); - + int radiusSquared = localX * localX + localY * localY; if (radiusSquared < mBoundInnerRadiusSquared || radiusSquared > mBoundOuterRadiusSquared) { @@ -246,7 +293,7 @@ public class ZoomRing extends View { isTouchingThumb = false; isInBounds = false; } - + int deltaThumbAndTouch = getDelta(touchAngle, mThumbAngle); int absoluteDeltaThumbAndTouch = deltaThumbAndTouch >= 0 ? deltaThumbAndTouch : -deltaThumbAndTouch; @@ -255,19 +302,35 @@ public class ZoomRing extends View { // Didn't grab close enough to the thumb isTouchingThumb = false; } - + if (mMode == MODE_IDLE) { - mMode = isTouchingThumb ? MODE_DRAG_THUMB : MODE_MOVE_ZOOM_RING; - + if (isTouchingThumb) { + mMode = MODE_DRAG_THUMB; + } else { + mMode = MODE_WAITING_FOR_MOVE_ZOOM_RING; + } + if (mCallback != null) { if (mMode == MODE_DRAG_THUMB) { onThumbDragStarted(touchAngle); - } else if (mMode == MODE_MOVE_ZOOM_RING) { + } else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { + mCallback.onZoomRingSetMovableHintVisible(true); + } + } + + } else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { + if (Math.abs(x - mPreviousDownX) > mTouchSlop || + Math.abs(y - mPreviousDownY) > mTouchSlop) { + /* Make sure the user has moved the slop amount before going into that mode. */ + mMode = MODE_MOVE_ZOOM_RING; + + if (mCallback != null) { mCallback.onZoomRingMovingStarted(); } } } - + + // Purposefully not an "else if" if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) { if (isInBounds) { onThumbDragged(touchAngle, mIsThumbAngleValid ? deltaThumbAndTouch : 0); @@ -277,13 +340,13 @@ public class ZoomRing extends View { } else if (mMode == MODE_MOVE_ZOOM_RING) { onZoomRingMoved(rawX, rawY); } - + return true; } - + private int getDelta(int angle1, int angle2) { int delta = angle1 - angle2; - + // Assume this is a result of crossing over the discontinuous 0 -> 2pi if (delta > PI_INT_MULTIPLIED || delta < -PI_INT_MULTIPLIED) { // Bring both the radians and previous angle onto a continuous range @@ -295,7 +358,7 @@ public class ZoomRing extends View { delta -= PI_INT_MULTIPLIED * 2; } } - + return delta; } @@ -303,46 +366,69 @@ public class ZoomRing extends View { mThumbDragStartAngle = startAngle; mCallback.onZoomRingThumbDraggingStarted(startAngle); } - + private void onThumbDragged(int touchAngle, int deltaAngle) { - mAcculumalatedThumbDrag += deltaAngle; - if (mAcculumalatedThumbDrag > mCallbackThreshold - || mAcculumalatedThumbDrag < -mCallbackThreshold) { + mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER); + int totalDeltaAngle = getDelta(touchAngle, mPreviousCallbackAngle); + if (totalDeltaAngle > mCallbackThreshold + || totalDeltaAngle < -mCallbackThreshold) { if (mCallback != null) { boolean canStillZoom = mCallback.onZoomRingThumbDragged( - mAcculumalatedThumbDrag / mCallbackThreshold, - mAcculumalatedThumbDrag, mThumbDragStartAngle, touchAngle); + totalDeltaAngle / mCallbackThreshold, + mThumbDragStartAngle, touchAngle); mDisabler.setEnabling(canStillZoom); + + if (canStillZoom) { + // TODO: we're trying the haptics to see how it goes with + // users, so we're ignoring the settings (for now) + performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK, + HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING | + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } } - mAcculumalatedThumbDrag = 0; + + // Get the closest tick and lock on there + mPreviousCallbackAngle = getClosestTickAngle(touchAngle); } - + setThumbAngle(touchAngle); mIsThumbAngleValid = true; } - + + private int getClosestTickAngle(int angle) { + int smallerAngleDistance = angle % mCallbackThreshold; + int smallerAngle = angle - smallerAngleDistance; + if (smallerAngleDistance < mCallbackThreshold / 2) { + // Closer to the smaller angle + return smallerAngle; + } else { + // Closer to the bigger angle (premodding) + return (smallerAngle + mCallbackThreshold) % (PI_INT_MULTIPLIED * 2); + } + } + private void onThumbDragStopped(int stopAngle) { mCallback.onZoomRingThumbDraggingStopped(stopAngle); } - + private void onZoomRingMoved(int x, int y) { if (mPreviousWidgetDragX != Integer.MIN_VALUE) { int deltaX = x - mPreviousWidgetDragX; int deltaY = y - mPreviousWidgetDragY; - + if (mCallback != null) { mCallback.onZoomRingMoved(deltaX, deltaY); } } - + mPreviousWidgetDragX = x; mPreviousWidgetDragY = y; } - + @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); - + if (!hasWindowFocus && mCallback != null) { mCallback.onZoomRingDismissed(); } @@ -353,22 +439,25 @@ public class ZoomRing extends View { // Convert from [-pi,pi] to {0,2pi] if (radians < 0) { - return -radians; + radians = -radians; } else if (radians > 0) { - return 2 * PI_INT_MULTIPLIED - radians; + radians = 2 * PI_INT_MULTIPLIED - radians; } else { - return 0; + radians = 0; } + + radians = radians - mZeroAngle; + return radians >= 0 ? radians : radians + 2 * PI_INT_MULTIPLIED; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - + if (mDrawThumb) { - mThumbDrawable.setBounds(mThumbCenterX - mThumbHalfWidth, mThumbCenterY - - mThumbHalfHeight, mThumbCenterX + mThumbHalfWidth, mThumbCenterY - + mThumbHalfHeight); + if (DRAW_TRAIL) { + mTrail.draw(canvas); + } mThumbDrawable.draw(canvas); } } @@ -409,12 +498,14 @@ public class ZoomRing extends View { } public interface OnZoomRingCallback { + void onZoomRingSetMovableHintVisible(boolean visible); + void onZoomRingMovingStarted(); boolean onZoomRingMoved(int deltaX, int deltaY); void onZoomRingMovingStopped(); void onZoomRingThumbDraggingStarted(int startAngle); - boolean onZoomRingThumbDragged(int numLevels, int dragAmount, int startAngle, int curAngle); + boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle); void onZoomRingThumbDraggingStopped(int endAngle); void onZoomRingDismissed(); |