summaryrefslogtreecommitdiffstats
path: root/core/java/android/widget/ZoomRing.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/widget/ZoomRing.java')
-rw-r--r--core/java/android/widget/ZoomRing.java271
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();