RotarySelector.java revision 88e037577f7db140e4ef88b77eefaa910e06e5f5
1e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen/* 2e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Copyright (C) 2009 The Android Open Source Project 3e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 4e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Licensed under the Apache License, Version 2.0 (the "License"); 5e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * you may not use this file except in compliance with the License. 6e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * You may obtain a copy of the License at 7e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 8e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * http://www.apache.org/licenses/LICENSE-2.0 9e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 10e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Unless required by applicable law or agreed to in writing, software 11e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * distributed under the License is distributed on an "AS IS" BASIS, 12e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * See the License for the specific language governing permissions and 14e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * limitations under the License. 15e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 16e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 17e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenpackage com.android.internal.widget; 18e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 19e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.content.Context; 20e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.content.res.Resources; 2174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaenimport android.content.res.TypedArray; 22e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.graphics.Canvas; 2374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaenimport android.graphics.Paint; 2474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaenimport android.graphics.Bitmap; 2574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaenimport android.graphics.BitmapFactory; 2674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaenimport android.graphics.Matrix; 27e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.graphics.drawable.Drawable; 28e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.os.Vibrator; 29e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.util.AttributeSet; 30e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.util.Log; 31e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.view.MotionEvent; 32e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.view.View; 33896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaenimport android.view.VelocityTracker; 34896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaenimport android.view.ViewConfiguration; 35896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaenimport android.view.animation.DecelerateInterpolator; 361ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaenimport static android.view.animation.AnimationUtils.currentAnimationTimeMillis; 37e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport com.android.internal.R; 38e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 39e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 40e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen/** 41e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Custom view that presents up to two items that are selectable by rotating a semi-circle from 42e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * left to right, or right to left. Used by incoming call screen, and the lock screen when no 43e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * security pattern is set. 44e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 45e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenpublic class RotarySelector extends View { 4674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen public static final int HORIZONTAL = 0; 4774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen public static final int VERTICAL = 1; 4874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 49e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final String LOG_TAG = "RotarySelector"; 50e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final boolean DBG = false; 51e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 52e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Listener for onDialTrigger() callbacks. 53e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private OnDialTriggerListener mOnDialTriggerListener; 54e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 55e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private float mDensity; 56e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 57e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // UI elements 5874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mBackground; 59c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mDimple; 60d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller private Bitmap mDimpleDim; 61e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 62c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mLeftHandleIcon; 63c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mRightHandleIcon; 64e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 6574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowShortLeftAndRight; 6674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowLongLeft; // Long arrow starting on the left, pointing clockwise 6774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowLongRight; // Long arrow starting on the right, pointing CCW 68e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 69e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // positions of the left and right handle 70e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mLeftHandleX; 71e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mRightHandleX; 72e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 73896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // current offset of rotary widget along the x axis 74896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mRotaryOffsetX = 0; 75e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 76e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // state of the animation used to bring the handle back to its start position when 77e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the user lets go before triggering an action 78e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private boolean mAnimating = false; 79896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private long mAnimationStartTime; 80052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen private long mAnimationDuration; 81896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mAnimatingDeltaXStart; // the animation will interpolate from this delta to zero 82896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mAnimatingDeltaXEnd; 83896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 84896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private DecelerateInterpolator mInterpolator; 85e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 8674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Paint mPaint = new Paint(); 8774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 8874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // used to rotate the background and arrow assets depending on orientation 8974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final Matrix mBgMatrix = new Matrix(); 9074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final Matrix mArrowMatrix = new Matrix(); 9174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 92e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 93e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * If the user is currently dragging something. 94e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 95e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mGrabbedState = NOTHING_GRABBED; 9688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int NOTHING_GRABBED = 0; 9788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int LEFT_HANDLE_GRABBED = 1; 9888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int RIGHT_HANDLE_GRABBED = 2; 99e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 100e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 101e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Whether the user has triggered something (e.g dragging the left handle all the way over to 102e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the right). 103e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 104e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private boolean mTriggered = false; 105e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 106e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Vibration (haptic feedback) 107e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private Vibrator mVibrator; 108896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private static final long VIBRATE_SHORT = 30; // msec 109896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private static final long VIBRATE_LONG = 60; // msec 110e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 111e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 112e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The drawable for the arrows need to be scrunched this many dips towards the rotary bg below 113e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * it. 114e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 115e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final int ARROW_SCRUNCH_DIP = 6; 116e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 117e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 118e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * How far inset the left and right circles should be 119e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 120e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final int EDGE_PADDING_DIP = 9; 121e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 122e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 1235fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen * How far from the edge of the screen the user must drag to trigger the event. 1245fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen */ 125052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen private static final int EDGE_TRIGGER_DIP = 100; 1265fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen 1275fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen /** 128e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Dimensions of arc in background drawable. 129e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 130e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen static final int OUTER_ROTARY_RADIUS_DIP = 390; 131e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen static final int ROTARY_STROKE_WIDTH_DIP = 83; 132052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen static final int SNAP_BACK_ANIMATION_DURATION_MILLIS = 300; 133052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen static final int SPIN_ANIMATION_DURATION_MILLIS = 800; 134e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 1355fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen private int mEdgeTriggerThresh; 136278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mDimpleWidth; 137278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mBackgroundWidth; 138278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mBackgroundHeight; 139896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private final int mOuterRadius; 140896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private final int mInnerRadius; 141896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mDimpleSpacing; 142896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 143896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private VelocityTracker mVelocityTracker; 144896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mMinimumVelocity; 145896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mMaximumVelocity; 146896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 147896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen /** 148896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * The number of dimples we are flinging when we do the "spin" animation. Used to know when to 149896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * wrap the icons back around so they "rotate back" onto the screen. 150896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * @see #updateAnimation() 151896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen */ 152896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mDimplesOfFling = 0; 153896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 15474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen /** 15574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * Either {@link #HORIZONTAL} or {@link #VERTICAL}. 15674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen */ 15774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private int mOrientation; 158896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 159e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 1601ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen public RotarySelector(Context context) { 1611ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen this(context, null); 1621ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 1631ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen 164e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 165e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Constructor used when this widget is created from a layout file. 166e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 167e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public RotarySelector(Context context, AttributeSet attrs) { 168e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen super(context, attrs); 16974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 17074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen TypedArray a = 17174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen context.obtainStyledAttributes(attrs, R.styleable.RotarySelector); 17274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mOrientation = a.getInt(R.styleable.RotarySelector_orientation, HORIZONTAL); 17374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen a.recycle(); 174e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 175e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen Resources r = getResources(); 176e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mDensity = r.getDisplayMetrics().density; 177e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (DBG) log("- Density: " + mDensity); 178e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 179e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Assets (all are BitmapDrawables). 18074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackground = getBitmapFor(R.drawable.jog_dial_bg); 181c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mDimple = getBitmapFor(R.drawable.jog_dial_dimple); 182d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller mDimpleDim = getBitmapFor(R.drawable.jog_dial_dimple_dim); 183e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 18474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowLongLeft = getBitmapFor(R.drawable.jog_dial_arrow_long_left_green); 18574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowLongRight = getBitmapFor(R.drawable.jog_dial_arrow_long_right_red); 18674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowShortLeftAndRight = getBitmapFor(R.drawable.jog_dial_arrow_short_left_and_right); 1871ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen 188896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInterpolator = new DecelerateInterpolator(1f); 1895fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen 1905fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen mEdgeTriggerThresh = (int) (mDensity * EDGE_TRIGGER_DIP); 191278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen 192c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mDimpleWidth = mDimple.getWidth(); 193278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen 19474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth = mBackground.getWidth(); 19574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundHeight = mBackground.getHeight(); 196896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius = (int) (mDensity * OUTER_ROTARY_RADIUS_DIP); 197896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius = (int) ((OUTER_ROTARY_RADIUS_DIP - ROTARY_STROKE_WIDTH_DIP) * mDensity); 198896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 199896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final ViewConfiguration configuration = ViewConfiguration.get(mContext); 200896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mMinimumVelocity = configuration.getScaledMinimumFlingVelocity() * 2; 201896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 202896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 203896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 20474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap getBitmapFor(int resId) { 20574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen return BitmapFactory.decodeResource(getContext().getResources(), resId); 20674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 20774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 208896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen @Override 209896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen protected void onSizeChanged(int w, int h, int oldw, int oldh) { 210896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen super.onSizeChanged(w, h, oldw, oldh); 211896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 21274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int edgePadding = (int) (EDGE_PADDING_DIP * mDensity); 21374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mLeftHandleX = edgePadding + mDimpleWidth / 2; 21474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int length = isHoriz() ? w : h; 21574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRightHandleX = length - edgePadding - mDimpleWidth / 2; 21674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mDimpleSpacing = (length / 2) - mLeftHandleX; 21774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 21874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // bg matrix only needs to be calculated once 21974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.setTranslate(0, 0); 22074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 22174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // set up matrix for translating drawing of background and arrow assets 22274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int left = w - mBackgroundHeight; 22374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.preRotate(-90, 0, 0); 22474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.postTranslate(left, h); 22574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 22674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 22774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.postTranslate(0, h - mBackgroundHeight); 22874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 22974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 230896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 23174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private boolean isHoriz() { 23274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen return mOrientation == HORIZONTAL; 233e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 234e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 235e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 236e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Sets the left handle icon to a given resource. 237e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 238e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The resource should refer to a Drawable object, or use 0 to remove 239e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the icon. 240e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 241e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param resId the resource ID. 242e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 243e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setLeftHandleResource(int resId) { 244e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (resId != 0) { 245c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mLeftHandleIcon = getBitmapFor(resId); 246e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 247e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen invalidate(); 248e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 249e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 250e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 251e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Sets the right handle icon to a given resource. 252e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 253e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The resource should refer to a Drawable object, or use 0 to remove 254e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the icon. 255e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 256e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param resId the resource ID. 257e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 258e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setRightHandleResource(int resId) { 259e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (resId != 0) { 260c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mRightHandleIcon = getBitmapFor(resId); 261e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 262e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen invalidate(); 263e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 264e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 265c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen 266e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 267e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 26874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int length = isHoriz() ? 26974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen MeasureSpec.getSize(widthMeasureSpec) : 27074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen MeasureSpec.getSize(heightMeasureSpec); 27174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int arrowScrunch = (int) (ARROW_SCRUNCH_DIP * mDensity); 27274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int arrowH = mArrowShortLeftAndRight.getHeight(); 273e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 274e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // by making the height less than arrow + bg, arrow and bg will be scrunched together, 275e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // overlaying somewhat (though on transparent portions of the drawable). 276e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // this works because the arrows are drawn from the top, and the rotary bg is drawn 277e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // from the bottom. 27874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int height = mBackgroundHeight + arrowH - arrowScrunch; 279e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 28074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 28174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen setMeasuredDimension(length, height); 28274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 28374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen setMeasuredDimension(height, length); 28474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 28574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 286e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 287e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 288e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen protected void onDraw(Canvas canvas) { 289e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen super.onDraw(canvas); 29074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 29174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int width = getWidth(); 29274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 29374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // DEBUG: draw bounding box around widget 29474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// mPaint.setColor(Color.RED); 29574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// mPaint.setStyle(Paint.Style.STROKE); 29674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// canvas.drawRect(0, 0, width, getHeight(), mPaint); 297e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 298e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int height = getHeight(); 299e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 300e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // update animating state before we draw anything 301278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen if (mAnimating) { 302896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen updateAnimation(); 303e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 304e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 305e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Background: 30674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mBackground, mBgMatrix, mPaint); 307e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 308e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Draw the correct arrow(s) depending on the current state: 30974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.reset(); 310e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen switch (mGrabbedState) { 311e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case NOTHING_GRABBED: 31274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen //mArrowShortLeftAndRight; 313e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 314e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case LEFT_HANDLE_GRABBED: 31574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.setTranslate(0, 0); 31674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 31774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.preRotate(-90, 0, 0); 31874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.postTranslate(0, height); 31974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 32074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mArrowLongLeft, mArrowMatrix, mPaint); 321e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 322e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case RIGHT_HANDLE_GRABBED: 32374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.setTranslate(0, 0); 32474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 32574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.preRotate(-90, 0, 0); 32674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // since bg width is > height of screen in landscape mode... 32774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.postTranslate(0, height + (mBackgroundWidth - height)); 32874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 32974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mArrowLongRight, mArrowMatrix, mPaint); 330e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 331e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen default: 332e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen throw new IllegalStateException("invalid mGrabbedState: " + mGrabbedState); 333e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 334e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 33574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int bgHeight = mBackgroundHeight; 33674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int bgTop = isHoriz() ? 33774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen height - bgHeight: 33874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen width - bgHeight; 33974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // DEBUG: draw circle bounding arc drawable: good sanity check we're doing the math 34074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // correctly 341e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen// float or = OUTER_ROTARY_RADIUS_DIP * mDensity; 34274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// final int vOffset = mBackgroundWidth - height; 34374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// final int midX = isHoriz() ? 34474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// width / 2 : 34574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// mBackgroundWidth / 2 - vOffset; 34674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// if (isHoriz()) { 34774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// canvas.drawCircle(midX, or + bgTop, or, mPaint); 34874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// } else { 34974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// canvas.drawCircle(or + bgTop, midX, or, mPaint); 35074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen// } 35174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 352d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller // dimple selection 353d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller Bitmap dimpleBitmap = mGrabbedState == NOTHING_GRABBED ? mDimple : mDimpleDim; 354d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller 35574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // left dimple / icon 356e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen { 357896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int xOffset = mLeftHandleX + mRotaryOffsetX; 358e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int drawableY = getYOnArc( 35974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 360896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 361896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 362e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen xOffset); 36374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 364d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller drawCentered(dimpleBitmap, canvas, xOffset, drawableY + bgTop); 36574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (mGrabbedState != RIGHT_HANDLE_GRABBED) { 36674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen drawCentered(mLeftHandleIcon, canvas, xOffset, drawableY + bgTop); 36774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 36874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 36974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // vertical 370d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller drawCentered(dimpleBitmap, canvas, drawableY + bgTop, height - xOffset); 37174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (mGrabbedState != RIGHT_HANDLE_GRABBED) { 37274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen drawCentered(mLeftHandleIcon, canvas, drawableY + bgTop, height - xOffset); 37374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 3745fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen } 375e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 376e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 37774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // center dimple 37874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen { 37974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int xOffset = isHoriz() ? 38074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen width / 2 + mRotaryOffsetX: 38174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen height / 2 + mRotaryOffsetX; 382e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int drawableY = getYOnArc( 38374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 384896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 385896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 386e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen xOffset); 387e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 38874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 389d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller drawCentered(dimpleBitmap, canvas, xOffset, drawableY + bgTop); 39074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 39174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // vertical 392d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller drawCentered(dimpleBitmap, canvas, drawableY + bgTop, height - xOffset); 39374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 394e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 395e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 39674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // right dimple / icon 397e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen { 398896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int xOffset = mRightHandleX + mRotaryOffsetX; 399e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int drawableY = getYOnArc( 40074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 401896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 402896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 403e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen xOffset); 404e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 40574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 406d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller drawCentered(dimpleBitmap, canvas, xOffset, drawableY + bgTop); 40774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (mGrabbedState != LEFT_HANDLE_GRABBED) { 40874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen drawCentered(mRightHandleIcon, canvas, xOffset, drawableY + bgTop); 40974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 41074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 41174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // vertical 412d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller drawCentered(dimpleBitmap, canvas, drawableY + bgTop, height - xOffset); 41374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (mGrabbedState != LEFT_HANDLE_GRABBED) { 41474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen drawCentered(mRightHandleIcon, canvas, drawableY + bgTop, height - xOffset); 41574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 4165fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen } 417e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 418e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 419896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // draw extra left hand dimples 420896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen int dimpleLeft = mRotaryOffsetX + mLeftHandleX - mDimpleSpacing; 421896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int halfdimple = mDimpleWidth / 2; 422896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen while (dimpleLeft > -halfdimple) { 423896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int drawableY = getYOnArc( 42474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 425896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 426896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 427896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleLeft); 428896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 42974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 430d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller drawCentered(dimpleBitmap, canvas, dimpleLeft, drawableY + bgTop); 43174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 432d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller drawCentered(dimpleBitmap, canvas, drawableY + bgTop, height - dimpleLeft); 43374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 434896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleLeft -= mDimpleSpacing; 435896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 436896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 437896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // draw extra right hand dimples 438896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen int dimpleRight = mRotaryOffsetX + mRightHandleX + mDimpleSpacing; 439896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int rightThresh = mRight + halfdimple; 440896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen while (dimpleRight < rightThresh) { 441896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int drawableY = getYOnArc( 44274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 443896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 444896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 445896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleRight); 446896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 44774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 448d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller drawCentered(dimpleBitmap, canvas, dimpleRight, drawableY + bgTop); 44974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 450d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller drawCentered(dimpleBitmap, canvas, drawableY + bgTop, height - dimpleRight); 45174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 452896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleRight += mDimpleSpacing; 453896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 454e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 455e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 456e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 45774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * Assuming bitmap is a bounding box around a piece of an arc drawn by two concentric circles 458e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * (as the background drawable for the rotary widget is), and given an x coordinate along the 459e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * drawable, return the y coordinate of a point on the arc that is between the two concentric 460e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * circles. The resulting y combined with the incoming x is a point along the circle in 461e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * between the two concentric circles. 462e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 46374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * @param backgroundWidth The width of the asset (the bottom of the box surrounding the arc). 464e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param innerRadius The radius of the circle that intersects the drawable at the bottom two 465e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * corders of the drawable (top two corners in terms of drawing coordinates). 466e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param outerRadius The radius of the circle who's top most point is the top center of the 467e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * drawable (bottom center in terms of drawing coordinates). 46874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * @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 469e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * in between the two concentric circles. 470e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 47174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private int getYOnArc(int backgroundWidth, int innerRadius, int outerRadius, int x) { 472e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 473e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the hypotenuse 474e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int halfWidth = (outerRadius - innerRadius) / 2; 475e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int middleRadius = innerRadius + halfWidth; 476e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 477e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the bottom leg of the triangle 47874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int triangleBottom = (backgroundWidth / 2) - x; 479e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 480e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // "Our offense is like the pythagorean theorem: There is no answer!" - Shaquille O'Neal 481e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int triangleY = 482e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen (int) Math.sqrt(middleRadius * middleRadius - triangleBottom * triangleBottom); 483e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 484e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // convert to drawing coordinates: 485e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // middleRadius - triangleY = 486e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the vertical distance from the outer edge of the circle to the desired point 487e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // from there we add the distance from the top of the drawable to the middle circle 488e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return middleRadius - triangleY + halfWidth; 489e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 490e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 491e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 492e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Handle touch screen events. 493e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 494e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param event The motion event. 495e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @return True if the event was handled, false otherwise. 496e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 497e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 498e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public boolean onTouchEvent(MotionEvent event) { 499278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen if (mAnimating) { 500e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return true; 501e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 502896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker == null) { 503896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = VelocityTracker.obtain(); 504896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 505896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.addMovement(event); 506896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 50774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int height = getHeight(); 508e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 50974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int eventX = isHoriz() ? 51074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) event.getX(): 51174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen height - ((int) event.getY()); 512278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen final int hitWindow = mDimpleWidth; 513e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 5141ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen final int action = event.getAction(); 5151ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen switch (action) { 5161ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_DOWN: 5171ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-down"); 5181ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = false; 5191ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState != NOTHING_GRABBED) { 5201ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen reset(); 5211ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 522e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 5231ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (eventX < mLeftHandleX + hitWindow) { 524896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mLeftHandleX; 52588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(LEFT_HANDLE_GRABBED); 5261ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5271ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen vibrate(VIBRATE_SHORT); 5281ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (eventX > mRightHandleX - hitWindow) { 529896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mRightHandleX; 53088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(RIGHT_HANDLE_GRABBED); 5311ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5321ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen vibrate(VIBRATE_SHORT); 533e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 5341ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 535e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 5361ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_MOVE: 5371ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-move"); 5381ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState == LEFT_HANDLE_GRABBED) { 539896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mLeftHandleX; 5401ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 54174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rightThresh = isHoriz() ? getRight() : height; 54274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (eventX >= rightThresh - mEdgeTriggerThresh && !mTriggered) { 5431ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = true; 544278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen dispatchTriggerEvent(OnDialTriggerListener.LEFT_HANDLE); 545896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final VelocityTracker velocityTracker = mVelocityTracker; 546896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 54774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rawVelocity = isHoriz() ? 54874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) velocityTracker.getXVelocity(): 54974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen -(int) velocityTracker.getYVelocity(); 55074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int velocity = Math.max(mMinimumVelocity, rawVelocity); 551896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = Math.max( 552896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 8, 553896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen Math.abs(velocity / mDimpleSpacing)); 554896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimationWithVelocity( 555896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen eventX - mLeftHandleX, 556896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling * mDimpleSpacing, 557896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocity); 5581ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5591ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (mGrabbedState == RIGHT_HANDLE_GRABBED) { 560896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mRightHandleX; 5611ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5625fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen if (eventX <= mEdgeTriggerThresh && !mTriggered) { 5631ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = true; 564278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen dispatchTriggerEvent(OnDialTriggerListener.RIGHT_HANDLE); 565896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final VelocityTracker velocityTracker = mVelocityTracker; 566896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 56774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rawVelocity = isHoriz() ? 56874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) velocityTracker.getXVelocity(): 56974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen - (int) velocityTracker.getYVelocity(); 57074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int velocity = Math.min(-mMinimumVelocity, rawVelocity); 571896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = Math.max( 572896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 8, 573896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen Math.abs(velocity / mDimpleSpacing)); 574896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimationWithVelocity( 575896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen eventX - mRightHandleX, 576896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen -(mDimplesOfFling * mDimpleSpacing), 577896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocity); 5781ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5791ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5801ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 5811ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_UP: 5821ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-up"); 5831ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen // handle animating back to start if they didn't trigger 5841ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState == LEFT_HANDLE_GRABBED 5851ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen && Math.abs(eventX - mLeftHandleX) > 5) { 586278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen // set up "snap back" animation 587896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimation(eventX - mLeftHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); 5881ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (mGrabbedState == RIGHT_HANDLE_GRABBED 5891ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen && Math.abs(eventX - mRightHandleX) > 5) { 590278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen // set up "snap back" animation 591896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimation(eventX - mRightHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); 5921ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 593896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = 0; 59488e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 5951ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 596896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker != null) { 597896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.recycle(); // wishin' we had generational GC 598896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = null; 599896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 6001ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 6011ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_CANCEL: 6021ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-cancel"); 6031ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen reset(); 6041ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 605896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker != null) { 606896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.recycle(); 607896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = null; 608896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 6091ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 610e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 611e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return true; 612e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 613e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 614896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void startAnimation(int startX, int endX, int duration) { 615896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimating = true; 616896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationStartTime = currentAnimationTimeMillis(); 617896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationDuration = duration; 618896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXStart = startX; 619896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXEnd = endX; 62088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 621896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = 0; 622896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 623896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 624896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 625896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void startAnimationWithVelocity(int startX, int endX, int pixelsPerSecond) { 626896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimating = true; 627896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationStartTime = currentAnimationTimeMillis(); 628896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationDuration = 1000 * (endX - startX) / pixelsPerSecond; 629896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXStart = startX; 630896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXEnd = endX; 63188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 632896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 633896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 634896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 635896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void updateAnimation() { 636896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final long millisSoFar = currentAnimationTimeMillis() - mAnimationStartTime; 637896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final long millisLeft = mAnimationDuration - millisSoFar; 638896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int totalDeltaX = mAnimatingDeltaXStart - mAnimatingDeltaXEnd; 63974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final boolean goingRight = totalDeltaX < 0; 640896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (DBG) log("millisleft for animating: " + millisLeft); 641896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (millisLeft <= 0) { 642896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen reset(); 643896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen return; 644896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 645896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // from 0 to 1 as animation progresses 646896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen float interpolation = 647896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInterpolator.getInterpolation((float) millisSoFar / mAnimationDuration); 648896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int dx = (int) (totalDeltaX * (1 - interpolation)); 649896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = mAnimatingDeltaXEnd + dx; 65074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 65174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // once we have gone far enough to animate the current buttons off screen, we start 65274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // wrapping the offset back to the other side so that when the animation is finished, 65374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // the buttons will come back into their original places. 654896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mDimplesOfFling > 0) { 655ff9c54b7214e99b3182f53f3089da0503f3edddcKarl Rosaen if (!goingRight && mRotaryOffsetX < -3 * mDimpleSpacing) { 656896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // wrap around on fling left 65774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRotaryOffsetX += mDimplesOfFling * mDimpleSpacing; 65874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else if (goingRight && mRotaryOffsetX > 3 * mDimpleSpacing) { 659896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // wrap around on fling right 66074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRotaryOffsetX -= mDimplesOfFling * mDimpleSpacing; 661896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 662896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 663896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 664896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 665896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 666e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private void reset() { 667e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mAnimating = false; 668896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = 0; 669896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = 0; 67088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 671e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mTriggered = false; 672e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 673e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 674e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 675e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Triggers haptic feedback. 676e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 677e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private synchronized void vibrate(long duration) { 678e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (mVibrator == null) { 679896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVibrator = (android.os.Vibrator) 680896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen getContext().getSystemService(Context.VIBRATOR_SERVICE); 681e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 682e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mVibrator.vibrate(duration); 683e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 684e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 685e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 686c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen * Draw the bitmap so that it's centered 687c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen * on the point (x,y), then draws it using specified canvas. 688e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * TODO: is there already a utility method somewhere for this? 689e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 690c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private void drawCentered(Bitmap d, Canvas c, int x, int y) { 691c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen int w = d.getWidth(); 692c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen int h = d.getHeight(); 693c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen 694c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen c.drawBitmap(d, x - (w / 2), y - (h / 2), mPaint); 695e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 696e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 697e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 698e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 699e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Registers a callback to be invoked when the dial 700e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * is "triggered" by rotating it one way or the other. 701e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 702e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param l the OnDialTriggerListener to attach to this view 703e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 704e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setOnDialTriggerListener(OnDialTriggerListener l) { 705e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mOnDialTriggerListener = l; 706e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 707e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 708e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 709e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Dispatches a trigger event to our listener. 710e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 711278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private void dispatchTriggerEvent(int whichHandle) { 712e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen vibrate(VIBRATE_LONG); 713e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (mOnDialTriggerListener != null) { 714278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen mOnDialTriggerListener.onDialTrigger(this, whichHandle); 715e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 716e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 717e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 718e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 71988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * Sets the current grabbed state, and dispatches a grabbed state change 72088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * event to our listener. 72188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown */ 72288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown private void setGrabbedState(int newState) { 72388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown if (newState != mGrabbedState) { 72488e037577f7db140e4ef88b77eefaa910e06e5f5David Brown mGrabbedState = newState; 72588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown if (mOnDialTriggerListener != null) { 72688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown mOnDialTriggerListener.onGrabbedStateChange(this, mGrabbedState); 72788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 72888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 72988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 73088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown 73188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown /** 732e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Interface definition for a callback to be invoked when the dial 733e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * is "triggered" by rotating it one way or the other. 734e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 735e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public interface OnDialTriggerListener { 736e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 737e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The dial was triggered because the user grabbed the left handle, 738e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * and rotated the dial clockwise. 739e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 740e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public static final int LEFT_HANDLE = 1; 741e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 742e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 743e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The dial was triggered because the user grabbed the right handle, 744e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * and rotated the dial counterclockwise. 745e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 746e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public static final int RIGHT_HANDLE = 2; 747e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 748e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 749e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Called when the dial is triggered. 750e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 751e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param v The view that was triggered 752e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param whichHandle Which "dial handle" the user grabbed, 753278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen * either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}. 754e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 755278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen void onDialTrigger(View v, int whichHandle); 75688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown 75788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown /** 75888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * Called when the "grabbed state" changes (i.e. when 75988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * the user either grabs or releases one of the handles.) 76088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * 76188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * @param v the view that was triggered 76288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * @param grabbedState the new state: either {@link #NOTHING_GRABBED}, 76388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * {@link #LEFT_HANDLE_GRABBED}, or {@link #RIGHT_HANDLE_GRABBED}. 76488e037577f7db140e4ef88b77eefaa910e06e5f5David Brown */ 76588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown void onGrabbedStateChange(View v, int grabbedState); 766e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 767e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 768e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 769e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Debugging / testing code 770e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 771e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private void log(String msg) { 772e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen Log.d(LOG_TAG, msg); 773e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 774e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen} 775