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