Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2009 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.internal.widget; |
| 18 | |
| 19 | import android.content.Context; |
| 20 | import android.content.res.Resources; |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 21 | import android.content.res.TypedArray; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 22 | import android.graphics.Canvas; |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 23 | import android.graphics.Paint; |
| 24 | import android.graphics.Bitmap; |
| 25 | import android.graphics.BitmapFactory; |
| 26 | import android.graphics.Matrix; |
Jeff Sharkey | 723a725 | 2012-10-12 14:26:31 -0700 | [diff] [blame] | 27 | import android.os.UserHandle; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 28 | import android.os.Vibrator; |
Jeff Sharkey | 723a725 | 2012-10-12 14:26:31 -0700 | [diff] [blame] | 29 | import android.provider.Settings; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 30 | import android.util.AttributeSet; |
| 31 | import android.util.Log; |
| 32 | import android.view.MotionEvent; |
| 33 | import android.view.View; |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 34 | import android.view.VelocityTracker; |
| 35 | import android.view.ViewConfiguration; |
| 36 | import android.view.animation.DecelerateInterpolator; |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 37 | import static android.view.animation.AnimationUtils.currentAnimationTimeMillis; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 38 | import com.android.internal.R; |
| 39 | |
| 40 | |
| 41 | /** |
| 42 | * Custom view that presents up to two items that are selectable by rotating a semi-circle from |
| 43 | * left to right, or right to left. Used by incoming call screen, and the lock screen when no |
| 44 | * security pattern is set. |
| 45 | */ |
| 46 | public class RotarySelector extends View { |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 47 | public static final int HORIZONTAL = 0; |
| 48 | public static final int VERTICAL = 1; |
| 49 | |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 50 | private static final String LOG_TAG = "RotarySelector"; |
| 51 | private static final boolean DBG = false; |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 52 | private static final boolean VISUAL_DEBUG = false; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 53 | |
| 54 | // Listener for onDialTrigger() callbacks. |
| 55 | private OnDialTriggerListener mOnDialTriggerListener; |
| 56 | |
| 57 | private float mDensity; |
| 58 | |
| 59 | // UI elements |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 60 | private Bitmap mBackground; |
Karl Rosaen | c8ad6dc | 2009-09-25 11:34:50 -0700 | [diff] [blame] | 61 | private Bitmap mDimple; |
Jim Miller | d9b6f14 | 2009-09-30 22:50:01 -0700 | [diff] [blame] | 62 | private Bitmap mDimpleDim; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 63 | |
Karl Rosaen | c8ad6dc | 2009-09-25 11:34:50 -0700 | [diff] [blame] | 64 | private Bitmap mLeftHandleIcon; |
| 65 | private Bitmap mRightHandleIcon; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 66 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 67 | private Bitmap mArrowShortLeftAndRight; |
| 68 | private Bitmap mArrowLongLeft; // Long arrow starting on the left, pointing clockwise |
| 69 | private Bitmap mArrowLongRight; // Long arrow starting on the right, pointing CCW |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 70 | |
| 71 | // positions of the left and right handle |
| 72 | private int mLeftHandleX; |
| 73 | private int mRightHandleX; |
| 74 | |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 75 | // current offset of rotary widget along the x axis |
| 76 | private int mRotaryOffsetX = 0; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 77 | |
| 78 | // state of the animation used to bring the handle back to its start position when |
| 79 | // the user lets go before triggering an action |
| 80 | private boolean mAnimating = false; |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 81 | private long mAnimationStartTime; |
Karl Rosaen | 052e187 | 2009-09-20 15:03:20 -0700 | [diff] [blame] | 82 | private long mAnimationDuration; |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 83 | private int mAnimatingDeltaXStart; // the animation will interpolate from this delta to zero |
| 84 | private int mAnimatingDeltaXEnd; |
| 85 | |
| 86 | private DecelerateInterpolator mInterpolator; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 87 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 88 | private Paint mPaint = new Paint(); |
| 89 | |
| 90 | // used to rotate the background and arrow assets depending on orientation |
| 91 | final Matrix mBgMatrix = new Matrix(); |
| 92 | final Matrix mArrowMatrix = new Matrix(); |
| 93 | |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 94 | /** |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 95 | * If the user is currently dragging something. |
| 96 | */ |
| 97 | private int mGrabbedState = NOTHING_GRABBED; |
David Brown | 88e0375 | 2009-10-01 19:25:54 -0700 | [diff] [blame] | 98 | public static final int NOTHING_GRABBED = 0; |
| 99 | public static final int LEFT_HANDLE_GRABBED = 1; |
| 100 | public static final int RIGHT_HANDLE_GRABBED = 2; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 101 | |
| 102 | /** |
| 103 | * Whether the user has triggered something (e.g dragging the left handle all the way over to |
| 104 | * the right). |
| 105 | */ |
| 106 | private boolean mTriggered = false; |
| 107 | |
| 108 | // Vibration (haptic feedback) |
| 109 | private Vibrator mVibrator; |
Jim Miller | 9485aec | 2009-10-08 18:49:53 -0700 | [diff] [blame] | 110 | private static final long VIBRATE_SHORT = 20; // msec |
| 111 | private static final long VIBRATE_LONG = 20; // msec |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 112 | |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 113 | /** |
| 114 | * The drawable for the arrows need to be scrunched this many dips towards the rotary bg below |
| 115 | * it. |
| 116 | */ |
| 117 | private static final int ARROW_SCRUNCH_DIP = 6; |
| 118 | |
| 119 | /** |
| 120 | * How far inset the left and right circles should be |
| 121 | */ |
| 122 | private static final int EDGE_PADDING_DIP = 9; |
| 123 | |
| 124 | /** |
Karl Rosaen | 5fef93b | 2009-09-17 17:21:18 -0700 | [diff] [blame] | 125 | * How far from the edge of the screen the user must drag to trigger the event. |
| 126 | */ |
Karl Rosaen | 052e187 | 2009-09-20 15:03:20 -0700 | [diff] [blame] | 127 | private static final int EDGE_TRIGGER_DIP = 100; |
Karl Rosaen | 5fef93b | 2009-09-17 17:21:18 -0700 | [diff] [blame] | 128 | |
| 129 | /** |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 130 | * Dimensions of arc in background drawable. |
| 131 | */ |
| 132 | static final int OUTER_ROTARY_RADIUS_DIP = 390; |
| 133 | static final int ROTARY_STROKE_WIDTH_DIP = 83; |
Karl Rosaen | 052e187 | 2009-09-20 15:03:20 -0700 | [diff] [blame] | 134 | static final int SNAP_BACK_ANIMATION_DURATION_MILLIS = 300; |
| 135 | static final int SPIN_ANIMATION_DURATION_MILLIS = 800; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 136 | |
Karl Rosaen | 5fef93b | 2009-09-17 17:21:18 -0700 | [diff] [blame] | 137 | private int mEdgeTriggerThresh; |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 138 | private int mDimpleWidth; |
| 139 | private int mBackgroundWidth; |
| 140 | private int mBackgroundHeight; |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 141 | private final int mOuterRadius; |
| 142 | private final int mInnerRadius; |
| 143 | private int mDimpleSpacing; |
| 144 | |
| 145 | private VelocityTracker mVelocityTracker; |
| 146 | private int mMinimumVelocity; |
| 147 | private int mMaximumVelocity; |
| 148 | |
| 149 | /** |
| 150 | * The number of dimples we are flinging when we do the "spin" animation. Used to know when to |
| 151 | * wrap the icons back around so they "rotate back" onto the screen. |
| 152 | * @see #updateAnimation() |
| 153 | */ |
| 154 | private int mDimplesOfFling = 0; |
| 155 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 156 | /** |
| 157 | * Either {@link #HORIZONTAL} or {@link #VERTICAL}. |
| 158 | */ |
| 159 | private int mOrientation; |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 160 | |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 161 | |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 162 | public RotarySelector(Context context) { |
| 163 | this(context, null); |
| 164 | } |
| 165 | |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 166 | /** |
| 167 | * Constructor used when this widget is created from a layout file. |
| 168 | */ |
| 169 | public RotarySelector(Context context, AttributeSet attrs) { |
| 170 | super(context, attrs); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 171 | |
| 172 | TypedArray a = |
| 173 | context.obtainStyledAttributes(attrs, R.styleable.RotarySelector); |
| 174 | mOrientation = a.getInt(R.styleable.RotarySelector_orientation, HORIZONTAL); |
| 175 | a.recycle(); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 176 | |
| 177 | Resources r = getResources(); |
| 178 | mDensity = r.getDisplayMetrics().density; |
| 179 | if (DBG) log("- Density: " + mDensity); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 180 | |
| 181 | // Assets (all are BitmapDrawables). |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 182 | mBackground = getBitmapFor(R.drawable.jog_dial_bg); |
Karl Rosaen | c8ad6dc | 2009-09-25 11:34:50 -0700 | [diff] [blame] | 183 | mDimple = getBitmapFor(R.drawable.jog_dial_dimple); |
Jim Miller | d9b6f14 | 2009-09-30 22:50:01 -0700 | [diff] [blame] | 184 | mDimpleDim = getBitmapFor(R.drawable.jog_dial_dimple_dim); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 185 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 186 | mArrowLongLeft = getBitmapFor(R.drawable.jog_dial_arrow_long_left_green); |
| 187 | mArrowLongRight = getBitmapFor(R.drawable.jog_dial_arrow_long_right_red); |
| 188 | mArrowShortLeftAndRight = getBitmapFor(R.drawable.jog_dial_arrow_short_left_and_right); |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 189 | |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 190 | mInterpolator = new DecelerateInterpolator(1f); |
Karl Rosaen | 5fef93b | 2009-09-17 17:21:18 -0700 | [diff] [blame] | 191 | |
| 192 | mEdgeTriggerThresh = (int) (mDensity * EDGE_TRIGGER_DIP); |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 193 | |
Karl Rosaen | c8ad6dc | 2009-09-25 11:34:50 -0700 | [diff] [blame] | 194 | mDimpleWidth = mDimple.getWidth(); |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 195 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 196 | mBackgroundWidth = mBackground.getWidth(); |
| 197 | mBackgroundHeight = mBackground.getHeight(); |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 198 | mOuterRadius = (int) (mDensity * OUTER_ROTARY_RADIUS_DIP); |
| 199 | mInnerRadius = (int) ((OUTER_ROTARY_RADIUS_DIP - ROTARY_STROKE_WIDTH_DIP) * mDensity); |
| 200 | |
| 201 | final ViewConfiguration configuration = ViewConfiguration.get(mContext); |
| 202 | mMinimumVelocity = configuration.getScaledMinimumFlingVelocity() * 2; |
| 203 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); |
| 204 | } |
| 205 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 206 | private Bitmap getBitmapFor(int resId) { |
| 207 | return BitmapFactory.decodeResource(getContext().getResources(), resId); |
| 208 | } |
| 209 | |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 210 | @Override |
| 211 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| 212 | super.onSizeChanged(w, h, oldw, oldh); |
| 213 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 214 | final int edgePadding = (int) (EDGE_PADDING_DIP * mDensity); |
| 215 | mLeftHandleX = edgePadding + mDimpleWidth / 2; |
| 216 | final int length = isHoriz() ? w : h; |
| 217 | mRightHandleX = length - edgePadding - mDimpleWidth / 2; |
| 218 | mDimpleSpacing = (length / 2) - mLeftHandleX; |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 219 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 220 | // bg matrix only needs to be calculated once |
| 221 | mBgMatrix.setTranslate(0, 0); |
| 222 | if (!isHoriz()) { |
| 223 | // set up matrix for translating drawing of background and arrow assets |
| 224 | final int left = w - mBackgroundHeight; |
| 225 | mBgMatrix.preRotate(-90, 0, 0); |
| 226 | mBgMatrix.postTranslate(left, h); |
| 227 | |
| 228 | } else { |
| 229 | mBgMatrix.postTranslate(0, h - mBackgroundHeight); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | private boolean isHoriz() { |
| 234 | return mOrientation == HORIZONTAL; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 235 | } |
| 236 | |
| 237 | /** |
| 238 | * Sets the left handle icon to a given resource. |
| 239 | * |
| 240 | * The resource should refer to a Drawable object, or use 0 to remove |
| 241 | * the icon. |
| 242 | * |
| 243 | * @param resId the resource ID. |
| 244 | */ |
| 245 | public void setLeftHandleResource(int resId) { |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 246 | if (resId != 0) { |
Karl Rosaen | c8ad6dc | 2009-09-25 11:34:50 -0700 | [diff] [blame] | 247 | mLeftHandleIcon = getBitmapFor(resId); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 248 | } |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 249 | invalidate(); |
| 250 | } |
| 251 | |
| 252 | /** |
| 253 | * Sets the right handle icon to a given resource. |
| 254 | * |
| 255 | * The resource should refer to a Drawable object, or use 0 to remove |
| 256 | * the icon. |
| 257 | * |
| 258 | * @param resId the resource ID. |
| 259 | */ |
| 260 | public void setRightHandleResource(int resId) { |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 261 | if (resId != 0) { |
Karl Rosaen | c8ad6dc | 2009-09-25 11:34:50 -0700 | [diff] [blame] | 262 | mRightHandleIcon = getBitmapFor(resId); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 263 | } |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 264 | invalidate(); |
| 265 | } |
| 266 | |
Karl Rosaen | c8ad6dc | 2009-09-25 11:34:50 -0700 | [diff] [blame] | 267 | |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 268 | @Override |
| 269 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 270 | final int length = isHoriz() ? |
| 271 | MeasureSpec.getSize(widthMeasureSpec) : |
| 272 | MeasureSpec.getSize(heightMeasureSpec); |
| 273 | final int arrowScrunch = (int) (ARROW_SCRUNCH_DIP * mDensity); |
| 274 | final int arrowH = mArrowShortLeftAndRight.getHeight(); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 275 | |
| 276 | // by making the height less than arrow + bg, arrow and bg will be scrunched together, |
| 277 | // overlaying somewhat (though on transparent portions of the drawable). |
| 278 | // this works because the arrows are drawn from the top, and the rotary bg is drawn |
| 279 | // from the bottom. |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 280 | final int height = mBackgroundHeight + arrowH - arrowScrunch; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 281 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 282 | if (isHoriz()) { |
| 283 | setMeasuredDimension(length, height); |
| 284 | } else { |
| 285 | setMeasuredDimension(height, length); |
| 286 | } |
| 287 | } |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 288 | |
| 289 | @Override |
| 290 | protected void onDraw(Canvas canvas) { |
| 291 | super.onDraw(canvas); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 292 | |
| 293 | final int width = getWidth(); |
| 294 | |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 295 | if (VISUAL_DEBUG) { |
| 296 | // draw bounding box around widget |
| 297 | mPaint.setColor(0xffff0000); |
| 298 | mPaint.setStyle(Paint.Style.STROKE); |
| 299 | canvas.drawRect(0, 0, width, getHeight(), mPaint); |
| 300 | } |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 301 | |
| 302 | final int height = getHeight(); |
| 303 | |
| 304 | // update animating state before we draw anything |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 305 | if (mAnimating) { |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 306 | updateAnimation(); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 307 | } |
| 308 | |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 309 | // Background: |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 310 | canvas.drawBitmap(mBackground, mBgMatrix, mPaint); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 311 | |
| 312 | // Draw the correct arrow(s) depending on the current state: |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 313 | mArrowMatrix.reset(); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 314 | switch (mGrabbedState) { |
| 315 | case NOTHING_GRABBED: |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 316 | //mArrowShortLeftAndRight; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 317 | break; |
| 318 | case LEFT_HANDLE_GRABBED: |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 319 | mArrowMatrix.setTranslate(0, 0); |
| 320 | if (!isHoriz()) { |
| 321 | mArrowMatrix.preRotate(-90, 0, 0); |
| 322 | mArrowMatrix.postTranslate(0, height); |
| 323 | } |
| 324 | canvas.drawBitmap(mArrowLongLeft, mArrowMatrix, mPaint); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 325 | break; |
| 326 | case RIGHT_HANDLE_GRABBED: |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 327 | mArrowMatrix.setTranslate(0, 0); |
| 328 | if (!isHoriz()) { |
| 329 | mArrowMatrix.preRotate(-90, 0, 0); |
| 330 | // since bg width is > height of screen in landscape mode... |
| 331 | mArrowMatrix.postTranslate(0, height + (mBackgroundWidth - height)); |
| 332 | } |
| 333 | canvas.drawBitmap(mArrowLongRight, mArrowMatrix, mPaint); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 334 | break; |
| 335 | default: |
| 336 | throw new IllegalStateException("invalid mGrabbedState: " + mGrabbedState); |
| 337 | } |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 338 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 339 | final int bgHeight = mBackgroundHeight; |
| 340 | final int bgTop = isHoriz() ? |
| 341 | height - bgHeight: |
| 342 | width - bgHeight; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 343 | |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 344 | if (VISUAL_DEBUG) { |
| 345 | // draw circle bounding arc drawable: good sanity check we're doing the math correctly |
| 346 | float or = OUTER_ROTARY_RADIUS_DIP * mDensity; |
| 347 | final int vOffset = mBackgroundWidth - height; |
| 348 | final int midX = isHoriz() ? width / 2 : mBackgroundWidth / 2 - vOffset; |
| 349 | if (isHoriz()) { |
| 350 | canvas.drawCircle(midX, or + bgTop, or, mPaint); |
| 351 | } else { |
| 352 | canvas.drawCircle(or + bgTop, midX, or, mPaint); |
| 353 | } |
| 354 | } |
Jim Miller | d9b6f14 | 2009-09-30 22:50:01 -0700 | [diff] [blame] | 355 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 356 | // left dimple / icon |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 357 | { |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 358 | final int xOffset = mLeftHandleX + mRotaryOffsetX; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 359 | final int drawableY = getYOnArc( |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 360 | mBackgroundWidth, |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 361 | mInnerRadius, |
| 362 | mOuterRadius, |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 363 | xOffset); |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 364 | final int x = isHoriz() ? xOffset : drawableY + bgTop; |
| 365 | final int y = isHoriz() ? drawableY + bgTop : height - xOffset; |
| 366 | if (mGrabbedState != RIGHT_HANDLE_GRABBED) { |
| 367 | drawCentered(mDimple, canvas, x, y); |
| 368 | drawCentered(mLeftHandleIcon, canvas, x, y); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 369 | } else { |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 370 | drawCentered(mDimpleDim, canvas, x, y); |
Karl Rosaen | 5fef93b | 2009-09-17 17:21:18 -0700 | [diff] [blame] | 371 | } |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 372 | } |
| 373 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 374 | // center dimple |
| 375 | { |
| 376 | final int xOffset = isHoriz() ? |
| 377 | width / 2 + mRotaryOffsetX: |
| 378 | height / 2 + mRotaryOffsetX; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 379 | final int drawableY = getYOnArc( |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 380 | mBackgroundWidth, |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 381 | mInnerRadius, |
| 382 | mOuterRadius, |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 383 | xOffset); |
| 384 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 385 | if (isHoriz()) { |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 386 | drawCentered(mDimpleDim, canvas, xOffset, drawableY + bgTop); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 387 | } else { |
| 388 | // vertical |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 389 | drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - xOffset); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 390 | } |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 391 | } |
| 392 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 393 | // right dimple / icon |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 394 | { |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 395 | final int xOffset = mRightHandleX + mRotaryOffsetX; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 396 | final int drawableY = getYOnArc( |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 397 | mBackgroundWidth, |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 398 | mInnerRadius, |
| 399 | mOuterRadius, |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 400 | xOffset); |
| 401 | |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 402 | final int x = isHoriz() ? xOffset : drawableY + bgTop; |
| 403 | final int y = isHoriz() ? drawableY + bgTop : height - xOffset; |
| 404 | if (mGrabbedState != LEFT_HANDLE_GRABBED) { |
| 405 | drawCentered(mDimple, canvas, x, y); |
| 406 | drawCentered(mRightHandleIcon, canvas, x, y); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 407 | } else { |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 408 | drawCentered(mDimpleDim, canvas, x, y); |
Karl Rosaen | 5fef93b | 2009-09-17 17:21:18 -0700 | [diff] [blame] | 409 | } |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 410 | } |
| 411 | |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 412 | // draw extra left hand dimples |
| 413 | int dimpleLeft = mRotaryOffsetX + mLeftHandleX - mDimpleSpacing; |
| 414 | final int halfdimple = mDimpleWidth / 2; |
| 415 | while (dimpleLeft > -halfdimple) { |
| 416 | final int drawableY = getYOnArc( |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 417 | mBackgroundWidth, |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 418 | mInnerRadius, |
| 419 | mOuterRadius, |
| 420 | dimpleLeft); |
| 421 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 422 | if (isHoriz()) { |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 423 | drawCentered(mDimpleDim, canvas, dimpleLeft, drawableY + bgTop); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 424 | } else { |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 425 | drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleLeft); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 426 | } |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 427 | dimpleLeft -= mDimpleSpacing; |
| 428 | } |
| 429 | |
| 430 | // draw extra right hand dimples |
| 431 | int dimpleRight = mRotaryOffsetX + mRightHandleX + mDimpleSpacing; |
| 432 | final int rightThresh = mRight + halfdimple; |
| 433 | while (dimpleRight < rightThresh) { |
| 434 | final int drawableY = getYOnArc( |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 435 | mBackgroundWidth, |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 436 | mInnerRadius, |
| 437 | mOuterRadius, |
| 438 | dimpleRight); |
| 439 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 440 | if (isHoriz()) { |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 441 | drawCentered(mDimpleDim, canvas, dimpleRight, drawableY + bgTop); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 442 | } else { |
Jim Miller | 5037e57 | 2009-10-05 13:00:58 -0700 | [diff] [blame] | 443 | drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleRight); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 444 | } |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 445 | dimpleRight += mDimpleSpacing; |
| 446 | } |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 447 | } |
| 448 | |
| 449 | /** |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 450 | * Assuming bitmap is a bounding box around a piece of an arc drawn by two concentric circles |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 451 | * (as the background drawable for the rotary widget is), and given an x coordinate along the |
| 452 | * drawable, return the y coordinate of a point on the arc that is between the two concentric |
| 453 | * circles. The resulting y combined with the incoming x is a point along the circle in |
| 454 | * between the two concentric circles. |
| 455 | * |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 456 | * @param backgroundWidth The width of the asset (the bottom of the box surrounding the arc). |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 457 | * @param innerRadius The radius of the circle that intersects the drawable at the bottom two |
| 458 | * corders of the drawable (top two corners in terms of drawing coordinates). |
| 459 | * @param outerRadius The radius of the circle who's top most point is the top center of the |
| 460 | * drawable (bottom center in terms of drawing coordinates). |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 461 | * @param x The distance along the x axis of the desired point. @return The y coordinate, in drawing coordinates, that will place (x, y) along the circle |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 462 | * in between the two concentric circles. |
| 463 | */ |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 464 | private int getYOnArc(int backgroundWidth, int innerRadius, int outerRadius, int x) { |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 465 | |
| 466 | // the hypotenuse |
| 467 | final int halfWidth = (outerRadius - innerRadius) / 2; |
| 468 | final int middleRadius = innerRadius + halfWidth; |
| 469 | |
| 470 | // the bottom leg of the triangle |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 471 | final int triangleBottom = (backgroundWidth / 2) - x; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 472 | |
| 473 | // "Our offense is like the pythagorean theorem: There is no answer!" - Shaquille O'Neal |
| 474 | final int triangleY = |
| 475 | (int) Math.sqrt(middleRadius * middleRadius - triangleBottom * triangleBottom); |
| 476 | |
| 477 | // convert to drawing coordinates: |
| 478 | // middleRadius - triangleY = |
| 479 | // the vertical distance from the outer edge of the circle to the desired point |
| 480 | // from there we add the distance from the top of the drawable to the middle circle |
| 481 | return middleRadius - triangleY + halfWidth; |
| 482 | } |
| 483 | |
| 484 | /** |
| 485 | * Handle touch screen events. |
| 486 | * |
| 487 | * @param event The motion event. |
| 488 | * @return True if the event was handled, false otherwise. |
| 489 | */ |
| 490 | @Override |
| 491 | public boolean onTouchEvent(MotionEvent event) { |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 492 | if (mAnimating) { |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 493 | return true; |
| 494 | } |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 495 | if (mVelocityTracker == null) { |
| 496 | mVelocityTracker = VelocityTracker.obtain(); |
| 497 | } |
| 498 | mVelocityTracker.addMovement(event); |
| 499 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 500 | final int height = getHeight(); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 501 | |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 502 | final int eventX = isHoriz() ? |
| 503 | (int) event.getX(): |
| 504 | height - ((int) event.getY()); |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 505 | final int hitWindow = mDimpleWidth; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 506 | |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 507 | final int action = event.getAction(); |
| 508 | switch (action) { |
| 509 | case MotionEvent.ACTION_DOWN: |
| 510 | if (DBG) log("touch-down"); |
| 511 | mTriggered = false; |
| 512 | if (mGrabbedState != NOTHING_GRABBED) { |
| 513 | reset(); |
| 514 | invalidate(); |
| 515 | } |
| 516 | if (eventX < mLeftHandleX + hitWindow) { |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 517 | mRotaryOffsetX = eventX - mLeftHandleX; |
David Brown | 88e0375 | 2009-10-01 19:25:54 -0700 | [diff] [blame] | 518 | setGrabbedState(LEFT_HANDLE_GRABBED); |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 519 | invalidate(); |
| 520 | vibrate(VIBRATE_SHORT); |
| 521 | } else if (eventX > mRightHandleX - hitWindow) { |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 522 | mRotaryOffsetX = eventX - mRightHandleX; |
David Brown | 88e0375 | 2009-10-01 19:25:54 -0700 | [diff] [blame] | 523 | setGrabbedState(RIGHT_HANDLE_GRABBED); |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 524 | invalidate(); |
| 525 | vibrate(VIBRATE_SHORT); |
| 526 | } |
| 527 | break; |
| 528 | |
| 529 | case MotionEvent.ACTION_MOVE: |
| 530 | if (DBG) log("touch-move"); |
| 531 | if (mGrabbedState == LEFT_HANDLE_GRABBED) { |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 532 | mRotaryOffsetX = eventX - mLeftHandleX; |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 533 | invalidate(); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 534 | final int rightThresh = isHoriz() ? getRight() : height; |
| 535 | if (eventX >= rightThresh - mEdgeTriggerThresh && !mTriggered) { |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 536 | mTriggered = true; |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 537 | dispatchTriggerEvent(OnDialTriggerListener.LEFT_HANDLE); |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 538 | final VelocityTracker velocityTracker = mVelocityTracker; |
| 539 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 540 | final int rawVelocity = isHoriz() ? |
| 541 | (int) velocityTracker.getXVelocity(): |
| 542 | -(int) velocityTracker.getYVelocity(); |
| 543 | final int velocity = Math.max(mMinimumVelocity, rawVelocity); |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 544 | mDimplesOfFling = Math.max( |
| 545 | 8, |
| 546 | Math.abs(velocity / mDimpleSpacing)); |
| 547 | startAnimationWithVelocity( |
| 548 | eventX - mLeftHandleX, |
| 549 | mDimplesOfFling * mDimpleSpacing, |
| 550 | velocity); |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 551 | } |
| 552 | } else if (mGrabbedState == RIGHT_HANDLE_GRABBED) { |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 553 | mRotaryOffsetX = eventX - mRightHandleX; |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 554 | invalidate(); |
Karl Rosaen | 5fef93b | 2009-09-17 17:21:18 -0700 | [diff] [blame] | 555 | if (eventX <= mEdgeTriggerThresh && !mTriggered) { |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 556 | mTriggered = true; |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 557 | dispatchTriggerEvent(OnDialTriggerListener.RIGHT_HANDLE); |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 558 | final VelocityTracker velocityTracker = mVelocityTracker; |
| 559 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 560 | final int rawVelocity = isHoriz() ? |
| 561 | (int) velocityTracker.getXVelocity(): |
| 562 | - (int) velocityTracker.getYVelocity(); |
| 563 | final int velocity = Math.min(-mMinimumVelocity, rawVelocity); |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 564 | mDimplesOfFling = Math.max( |
| 565 | 8, |
| 566 | Math.abs(velocity / mDimpleSpacing)); |
| 567 | startAnimationWithVelocity( |
| 568 | eventX - mRightHandleX, |
| 569 | -(mDimplesOfFling * mDimpleSpacing), |
| 570 | velocity); |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 571 | } |
| 572 | } |
| 573 | break; |
| 574 | case MotionEvent.ACTION_UP: |
| 575 | if (DBG) log("touch-up"); |
| 576 | // handle animating back to start if they didn't trigger |
| 577 | if (mGrabbedState == LEFT_HANDLE_GRABBED |
| 578 | && Math.abs(eventX - mLeftHandleX) > 5) { |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 579 | // set up "snap back" animation |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 580 | startAnimation(eventX - mLeftHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 581 | } else if (mGrabbedState == RIGHT_HANDLE_GRABBED |
| 582 | && Math.abs(eventX - mRightHandleX) > 5) { |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 583 | // set up "snap back" animation |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 584 | startAnimation(eventX - mRightHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 585 | } |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 586 | mRotaryOffsetX = 0; |
David Brown | 88e0375 | 2009-10-01 19:25:54 -0700 | [diff] [blame] | 587 | setGrabbedState(NOTHING_GRABBED); |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 588 | invalidate(); |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 589 | if (mVelocityTracker != null) { |
| 590 | mVelocityTracker.recycle(); // wishin' we had generational GC |
| 591 | mVelocityTracker = null; |
| 592 | } |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 593 | break; |
| 594 | case MotionEvent.ACTION_CANCEL: |
| 595 | if (DBG) log("touch-cancel"); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 596 | reset(); |
| 597 | invalidate(); |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 598 | if (mVelocityTracker != null) { |
| 599 | mVelocityTracker.recycle(); |
| 600 | mVelocityTracker = null; |
| 601 | } |
Karl Rosaen | 1ca654e | 2009-09-16 10:51:10 -0700 | [diff] [blame] | 602 | break; |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 603 | } |
| 604 | return true; |
| 605 | } |
| 606 | |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 607 | private void startAnimation(int startX, int endX, int duration) { |
| 608 | mAnimating = true; |
| 609 | mAnimationStartTime = currentAnimationTimeMillis(); |
| 610 | mAnimationDuration = duration; |
| 611 | mAnimatingDeltaXStart = startX; |
| 612 | mAnimatingDeltaXEnd = endX; |
David Brown | 88e0375 | 2009-10-01 19:25:54 -0700 | [diff] [blame] | 613 | setGrabbedState(NOTHING_GRABBED); |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 614 | mDimplesOfFling = 0; |
| 615 | invalidate(); |
| 616 | } |
| 617 | |
| 618 | private void startAnimationWithVelocity(int startX, int endX, int pixelsPerSecond) { |
| 619 | mAnimating = true; |
| 620 | mAnimationStartTime = currentAnimationTimeMillis(); |
| 621 | mAnimationDuration = 1000 * (endX - startX) / pixelsPerSecond; |
| 622 | mAnimatingDeltaXStart = startX; |
| 623 | mAnimatingDeltaXEnd = endX; |
David Brown | 88e0375 | 2009-10-01 19:25:54 -0700 | [diff] [blame] | 624 | setGrabbedState(NOTHING_GRABBED); |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 625 | invalidate(); |
| 626 | } |
| 627 | |
| 628 | private void updateAnimation() { |
| 629 | final long millisSoFar = currentAnimationTimeMillis() - mAnimationStartTime; |
| 630 | final long millisLeft = mAnimationDuration - millisSoFar; |
| 631 | final int totalDeltaX = mAnimatingDeltaXStart - mAnimatingDeltaXEnd; |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 632 | final boolean goingRight = totalDeltaX < 0; |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 633 | if (DBG) log("millisleft for animating: " + millisLeft); |
| 634 | if (millisLeft <= 0) { |
| 635 | reset(); |
| 636 | return; |
| 637 | } |
| 638 | // from 0 to 1 as animation progresses |
| 639 | float interpolation = |
| 640 | mInterpolator.getInterpolation((float) millisSoFar / mAnimationDuration); |
| 641 | final int dx = (int) (totalDeltaX * (1 - interpolation)); |
| 642 | mRotaryOffsetX = mAnimatingDeltaXEnd + dx; |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 643 | |
| 644 | // once we have gone far enough to animate the current buttons off screen, we start |
| 645 | // wrapping the offset back to the other side so that when the animation is finished, |
| 646 | // the buttons will come back into their original places. |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 647 | if (mDimplesOfFling > 0) { |
Karl Rosaen | ff9c54b | 2009-09-24 10:50:48 -0700 | [diff] [blame] | 648 | if (!goingRight && mRotaryOffsetX < -3 * mDimpleSpacing) { |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 649 | // wrap around on fling left |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 650 | mRotaryOffsetX += mDimplesOfFling * mDimpleSpacing; |
| 651 | } else if (goingRight && mRotaryOffsetX > 3 * mDimpleSpacing) { |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 652 | // wrap around on fling right |
Karl Rosaen | 74646ad | 2009-09-23 17:00:55 -0700 | [diff] [blame] | 653 | mRotaryOffsetX -= mDimplesOfFling * mDimpleSpacing; |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 654 | } |
| 655 | } |
| 656 | invalidate(); |
| 657 | } |
| 658 | |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 659 | private void reset() { |
| 660 | mAnimating = false; |
Karl Rosaen | 896264f | 2009-09-22 11:36:23 -0700 | [diff] [blame] | 661 | mRotaryOffsetX = 0; |
| 662 | mDimplesOfFling = 0; |
David Brown | 88e0375 | 2009-10-01 19:25:54 -0700 | [diff] [blame] | 663 | setGrabbedState(NOTHING_GRABBED); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 664 | mTriggered = false; |
| 665 | } |
| 666 | |
| 667 | /** |
| 668 | * Triggers haptic feedback. |
| 669 | */ |
| 670 | private synchronized void vibrate(long duration) { |
Jeff Sharkey | 723a725 | 2012-10-12 14:26:31 -0700 | [diff] [blame] | 671 | final boolean hapticEnabled = Settings.System.getIntForUser( |
| 672 | mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, |
| 673 | UserHandle.USER_CURRENT) != 0; |
| 674 | if (hapticEnabled) { |
| 675 | if (mVibrator == null) { |
| 676 | mVibrator = (android.os.Vibrator) getContext() |
| 677 | .getSystemService(Context.VIBRATOR_SERVICE); |
| 678 | } |
| 679 | mVibrator.vibrate(duration); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 680 | } |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 681 | } |
| 682 | |
| 683 | /** |
Karl Rosaen | c8ad6dc | 2009-09-25 11:34:50 -0700 | [diff] [blame] | 684 | * Draw the bitmap so that it's centered |
| 685 | * on the point (x,y), then draws it using specified canvas. |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 686 | * TODO: is there already a utility method somewhere for this? |
| 687 | */ |
Karl Rosaen | c8ad6dc | 2009-09-25 11:34:50 -0700 | [diff] [blame] | 688 | private void drawCentered(Bitmap d, Canvas c, int x, int y) { |
| 689 | int w = d.getWidth(); |
| 690 | int h = d.getHeight(); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 691 | |
Karl Rosaen | c8ad6dc | 2009-09-25 11:34:50 -0700 | [diff] [blame] | 692 | c.drawBitmap(d, x - (w / 2), y - (h / 2), mPaint); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 693 | } |
| 694 | |
| 695 | |
| 696 | /** |
| 697 | * Registers a callback to be invoked when the dial |
| 698 | * is "triggered" by rotating it one way or the other. |
| 699 | * |
| 700 | * @param l the OnDialTriggerListener to attach to this view |
| 701 | */ |
| 702 | public void setOnDialTriggerListener(OnDialTriggerListener l) { |
| 703 | mOnDialTriggerListener = l; |
| 704 | } |
| 705 | |
| 706 | /** |
| 707 | * Dispatches a trigger event to our listener. |
| 708 | */ |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 709 | private void dispatchTriggerEvent(int whichHandle) { |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 710 | vibrate(VIBRATE_LONG); |
| 711 | if (mOnDialTriggerListener != null) { |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 712 | mOnDialTriggerListener.onDialTrigger(this, whichHandle); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 713 | } |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 714 | } |
| 715 | |
| 716 | /** |
David Brown | 88e0375 | 2009-10-01 19:25:54 -0700 | [diff] [blame] | 717 | * Sets the current grabbed state, and dispatches a grabbed state change |
| 718 | * event to our listener. |
| 719 | */ |
| 720 | private void setGrabbedState(int newState) { |
| 721 | if (newState != mGrabbedState) { |
| 722 | mGrabbedState = newState; |
| 723 | if (mOnDialTriggerListener != null) { |
| 724 | mOnDialTriggerListener.onGrabbedStateChange(this, mGrabbedState); |
| 725 | } |
| 726 | } |
| 727 | } |
| 728 | |
| 729 | /** |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 730 | * Interface definition for a callback to be invoked when the dial |
| 731 | * is "triggered" by rotating it one way or the other. |
| 732 | */ |
| 733 | public interface OnDialTriggerListener { |
| 734 | /** |
| 735 | * The dial was triggered because the user grabbed the left handle, |
| 736 | * and rotated the dial clockwise. |
| 737 | */ |
| 738 | public static final int LEFT_HANDLE = 1; |
| 739 | |
| 740 | /** |
| 741 | * The dial was triggered because the user grabbed the right handle, |
| 742 | * and rotated the dial counterclockwise. |
| 743 | */ |
| 744 | public static final int RIGHT_HANDLE = 2; |
| 745 | |
| 746 | /** |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 747 | * Called when the dial is triggered. |
| 748 | * |
| 749 | * @param v The view that was triggered |
| 750 | * @param whichHandle Which "dial handle" the user grabbed, |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 751 | * either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}. |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 752 | */ |
Karl Rosaen | 278ec5d6 | 2009-09-20 09:48:53 -0700 | [diff] [blame] | 753 | void onDialTrigger(View v, int whichHandle); |
David Brown | 88e0375 | 2009-10-01 19:25:54 -0700 | [diff] [blame] | 754 | |
| 755 | /** |
| 756 | * Called when the "grabbed state" changes (i.e. when |
| 757 | * the user either grabs or releases one of the handles.) |
| 758 | * |
| 759 | * @param v the view that was triggered |
| 760 | * @param grabbedState the new state: either {@link #NOTHING_GRABBED}, |
| 761 | * {@link #LEFT_HANDLE_GRABBED}, or {@link #RIGHT_HANDLE_GRABBED}. |
| 762 | */ |
| 763 | void onGrabbedStateChange(View v, int grabbedState); |
Karl Rosaen | e4d95d0 | 2009-09-15 17:36:09 -0700 | [diff] [blame] | 764 | } |
| 765 | |
| 766 | |
| 767 | // Debugging / testing code |
| 768 | |
| 769 | private void log(String msg) { |
| 770 | Log.d(LOG_TAG, msg); |
| 771 | } |
| 772 | } |