RotarySelector.java revision 5037e57fd43bccf79be80bc140b33d1fa69abe13
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; 515037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller private static final boolean VISUAL_DEBUG = false; 52e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 53e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Listener for onDialTrigger() callbacks. 54e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private OnDialTriggerListener mOnDialTriggerListener; 55e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 56e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private float mDensity; 57e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 58e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // UI elements 5974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mBackground; 60c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mDimple; 61d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller private Bitmap mDimpleDim; 62e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 63c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mLeftHandleIcon; 64c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mRightHandleIcon; 65e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 6674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowShortLeftAndRight; 6774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowLongLeft; // Long arrow starting on the left, pointing clockwise 6874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowLongRight; // Long arrow starting on the right, pointing CCW 69e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 70e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // positions of the left and right handle 71e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mLeftHandleX; 72e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mRightHandleX; 73e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 74896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // current offset of rotary widget along the x axis 75896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mRotaryOffsetX = 0; 76e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 77e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // state of the animation used to bring the handle back to its start position when 78e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the user lets go before triggering an action 79e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private boolean mAnimating = false; 80896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private long mAnimationStartTime; 81052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen private long mAnimationDuration; 82896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mAnimatingDeltaXStart; // the animation will interpolate from this delta to zero 83896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mAnimatingDeltaXEnd; 84896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 85896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private DecelerateInterpolator mInterpolator; 86e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 8774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Paint mPaint = new Paint(); 8874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 8974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // used to rotate the background and arrow assets depending on orientation 9074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final Matrix mBgMatrix = new Matrix(); 9174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final Matrix mArrowMatrix = new Matrix(); 9274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 93e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 94e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * If the user is currently dragging something. 95e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 96e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mGrabbedState = NOTHING_GRABBED; 9788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int NOTHING_GRABBED = 0; 9888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int LEFT_HANDLE_GRABBED = 1; 9988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int RIGHT_HANDLE_GRABBED = 2; 100e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 101e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 102e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Whether the user has triggered something (e.g dragging the left handle all the way over to 103e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the right). 104e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 105e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private boolean mTriggered = false; 106e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 107e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Vibration (haptic feedback) 108e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private Vibrator mVibrator; 109896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private static final long VIBRATE_SHORT = 30; // msec 110896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private static final long VIBRATE_LONG = 60; // msec 111e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 112e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 113e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The drawable for the arrows need to be scrunched this many dips towards the rotary bg below 114e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * it. 115e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 116e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final int ARROW_SCRUNCH_DIP = 6; 117e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 118e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 119e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * How far inset the left and right circles should be 120e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 121e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final int EDGE_PADDING_DIP = 9; 122e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 123e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 1245fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen * How far from the edge of the screen the user must drag to trigger the event. 1255fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen */ 126052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen private static final int EDGE_TRIGGER_DIP = 100; 1275fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen 1285fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen /** 129e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Dimensions of arc in background drawable. 130e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 131e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen static final int OUTER_ROTARY_RADIUS_DIP = 390; 132e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen static final int ROTARY_STROKE_WIDTH_DIP = 83; 133052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen static final int SNAP_BACK_ANIMATION_DURATION_MILLIS = 300; 134052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen static final int SPIN_ANIMATION_DURATION_MILLIS = 800; 135e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 1365fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen private int mEdgeTriggerThresh; 137278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mDimpleWidth; 138278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mBackgroundWidth; 139278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mBackgroundHeight; 140896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private final int mOuterRadius; 141896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private final int mInnerRadius; 142896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mDimpleSpacing; 143896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 144896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private VelocityTracker mVelocityTracker; 145896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mMinimumVelocity; 146896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mMaximumVelocity; 147896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 148896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen /** 149896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * The number of dimples we are flinging when we do the "spin" animation. Used to know when to 150896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * wrap the icons back around so they "rotate back" onto the screen. 151896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * @see #updateAnimation() 152896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen */ 153896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mDimplesOfFling = 0; 154896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 15574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen /** 15674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * Either {@link #HORIZONTAL} or {@link #VERTICAL}. 15774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen */ 15874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private int mOrientation; 159896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 160e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 1611ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen public RotarySelector(Context context) { 1621ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen this(context, null); 1631ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 1641ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen 165e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 166e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Constructor used when this widget is created from a layout file. 167e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 168e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public RotarySelector(Context context, AttributeSet attrs) { 169e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen super(context, attrs); 17074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 17174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen TypedArray a = 17274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen context.obtainStyledAttributes(attrs, R.styleable.RotarySelector); 17374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mOrientation = a.getInt(R.styleable.RotarySelector_orientation, HORIZONTAL); 17474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen a.recycle(); 175e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 176e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen Resources r = getResources(); 177e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mDensity = r.getDisplayMetrics().density; 178e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (DBG) log("- Density: " + mDensity); 179e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 180e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Assets (all are BitmapDrawables). 18174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackground = getBitmapFor(R.drawable.jog_dial_bg); 182c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mDimple = getBitmapFor(R.drawable.jog_dial_dimple); 183d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller mDimpleDim = getBitmapFor(R.drawable.jog_dial_dimple_dim); 184e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 18574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowLongLeft = getBitmapFor(R.drawable.jog_dial_arrow_long_left_green); 18674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowLongRight = getBitmapFor(R.drawable.jog_dial_arrow_long_right_red); 18774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowShortLeftAndRight = getBitmapFor(R.drawable.jog_dial_arrow_short_left_and_right); 1881ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen 189896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInterpolator = new DecelerateInterpolator(1f); 1905fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen 1915fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen mEdgeTriggerThresh = (int) (mDensity * EDGE_TRIGGER_DIP); 192278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen 193c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mDimpleWidth = mDimple.getWidth(); 194278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen 19574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth = mBackground.getWidth(); 19674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundHeight = mBackground.getHeight(); 197896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius = (int) (mDensity * OUTER_ROTARY_RADIUS_DIP); 198896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius = (int) ((OUTER_ROTARY_RADIUS_DIP - ROTARY_STROKE_WIDTH_DIP) * mDensity); 199896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 200896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final ViewConfiguration configuration = ViewConfiguration.get(mContext); 201896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mMinimumVelocity = configuration.getScaledMinimumFlingVelocity() * 2; 202896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 203896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 204896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 20574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap getBitmapFor(int resId) { 20674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen return BitmapFactory.decodeResource(getContext().getResources(), resId); 20774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 20874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 209896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen @Override 210896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen protected void onSizeChanged(int w, int h, int oldw, int oldh) { 211896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen super.onSizeChanged(w, h, oldw, oldh); 212896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 21374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int edgePadding = (int) (EDGE_PADDING_DIP * mDensity); 21474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mLeftHandleX = edgePadding + mDimpleWidth / 2; 21574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int length = isHoriz() ? w : h; 21674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRightHandleX = length - edgePadding - mDimpleWidth / 2; 21774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mDimpleSpacing = (length / 2) - mLeftHandleX; 21874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 21974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // bg matrix only needs to be calculated once 22074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.setTranslate(0, 0); 22174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 22274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // set up matrix for translating drawing of background and arrow assets 22374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int left = w - mBackgroundHeight; 22474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.preRotate(-90, 0, 0); 22574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.postTranslate(left, h); 22674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 22774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 22874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.postTranslate(0, h - mBackgroundHeight); 22974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 23074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 231896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 23274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private boolean isHoriz() { 23374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen return mOrientation == HORIZONTAL; 234e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 235e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 236e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 237e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Sets the left handle icon to a given resource. 238e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 239e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The resource should refer to a Drawable object, or use 0 to remove 240e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the icon. 241e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 242e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param resId the resource ID. 243e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 244e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setLeftHandleResource(int resId) { 245e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (resId != 0) { 246c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mLeftHandleIcon = getBitmapFor(resId); 247e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 248e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen invalidate(); 249e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 250e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 251e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 252e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Sets the right handle icon to a given resource. 253e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 254e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The resource should refer to a Drawable object, or use 0 to remove 255e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the icon. 256e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 257e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param resId the resource ID. 258e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 259e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setRightHandleResource(int resId) { 260e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (resId != 0) { 261c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mRightHandleIcon = getBitmapFor(resId); 262e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 263e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen invalidate(); 264e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 265e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 266c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen 267e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 268e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 26974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int length = isHoriz() ? 27074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen MeasureSpec.getSize(widthMeasureSpec) : 27174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen MeasureSpec.getSize(heightMeasureSpec); 27274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int arrowScrunch = (int) (ARROW_SCRUNCH_DIP * mDensity); 27374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int arrowH = mArrowShortLeftAndRight.getHeight(); 274e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 275e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // by making the height less than arrow + bg, arrow and bg will be scrunched together, 276e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // overlaying somewhat (though on transparent portions of the drawable). 277e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // this works because the arrows are drawn from the top, and the rotary bg is drawn 278e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // from the bottom. 27974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int height = mBackgroundHeight + arrowH - arrowScrunch; 280e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 28174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 28274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen setMeasuredDimension(length, height); 28374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 28474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen setMeasuredDimension(height, length); 28574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 28674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 287e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 288e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 289e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen protected void onDraw(Canvas canvas) { 290e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen super.onDraw(canvas); 29174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 29274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int width = getWidth(); 29374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 2945037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (VISUAL_DEBUG) { 2955037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller // draw bounding box around widget 2965037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller mPaint.setColor(0xffff0000); 2975037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller mPaint.setStyle(Paint.Style.STROKE); 2985037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller canvas.drawRect(0, 0, width, getHeight(), mPaint); 2995037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } 300e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 301e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int height = getHeight(); 302e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 303e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // update animating state before we draw anything 304278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen if (mAnimating) { 305896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen updateAnimation(); 306e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 307e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 308e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Background: 30974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mBackground, mBgMatrix, mPaint); 310e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 311e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Draw the correct arrow(s) depending on the current state: 31274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.reset(); 313e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen switch (mGrabbedState) { 314e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case NOTHING_GRABBED: 31574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen //mArrowShortLeftAndRight; 316e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 317e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case LEFT_HANDLE_GRABBED: 31874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.setTranslate(0, 0); 31974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 32074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.preRotate(-90, 0, 0); 32174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.postTranslate(0, height); 32274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 32374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mArrowLongLeft, mArrowMatrix, mPaint); 324e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 325e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case RIGHT_HANDLE_GRABBED: 32674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.setTranslate(0, 0); 32774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 32874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.preRotate(-90, 0, 0); 32974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // since bg width is > height of screen in landscape mode... 33074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.postTranslate(0, height + (mBackgroundWidth - height)); 33174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 33274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mArrowLongRight, mArrowMatrix, mPaint); 333e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 334e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen default: 335e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen throw new IllegalStateException("invalid mGrabbedState: " + mGrabbedState); 336e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 337e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 33874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int bgHeight = mBackgroundHeight; 33974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int bgTop = isHoriz() ? 34074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen height - bgHeight: 34174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen width - bgHeight; 3425037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller 3435037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (VISUAL_DEBUG) { 3445037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller // draw circle bounding arc drawable: good sanity check we're doing the math correctly 3455037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller float or = OUTER_ROTARY_RADIUS_DIP * mDensity; 3465037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int vOffset = mBackgroundWidth - height; 3475037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int midX = isHoriz() ? width / 2 : mBackgroundWidth / 2 - vOffset; 3485037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (isHoriz()) { 3495037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller canvas.drawCircle(midX, or + bgTop, or, mPaint); 3505037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } else { 3515037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller canvas.drawCircle(or + bgTop, midX, or, mPaint); 3525037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } 3535037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } 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); 3635037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int x = isHoriz() ? xOffset : drawableY + bgTop; 3645037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int y = isHoriz() ? drawableY + bgTop : height - xOffset; 3655037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (mGrabbedState != RIGHT_HANDLE_GRABBED) { 3665037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimple, canvas, x, y); 3675037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mLeftHandleIcon, canvas, x, y); 36874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 3695037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, x, y); 3705fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen } 371e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 372e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 37374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // center dimple 37474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen { 37574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int xOffset = isHoriz() ? 37674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen width / 2 + mRotaryOffsetX: 37774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen height / 2 + mRotaryOffsetX; 378e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int drawableY = getYOnArc( 37974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 380896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 381896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 382e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen xOffset); 383e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 38474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 3855037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, xOffset, drawableY + bgTop); 38674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 38774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // vertical 3885037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - xOffset); 38974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 390e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 391e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 39274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // right dimple / icon 393e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen { 394896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int xOffset = mRightHandleX + mRotaryOffsetX; 395e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int drawableY = getYOnArc( 39674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 397896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 398896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 399e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen xOffset); 400e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 4015037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int x = isHoriz() ? xOffset : drawableY + bgTop; 4025037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int y = isHoriz() ? drawableY + bgTop : height - xOffset; 4035037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (mGrabbedState != LEFT_HANDLE_GRABBED) { 4045037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimple, canvas, x, y); 4055037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mRightHandleIcon, canvas, x, y); 40674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 4075037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, x, y); 4085fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen } 409e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 410e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 411896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // draw extra left hand dimples 412896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen int dimpleLeft = mRotaryOffsetX + mLeftHandleX - mDimpleSpacing; 413896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int halfdimple = mDimpleWidth / 2; 414896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen while (dimpleLeft > -halfdimple) { 415896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int drawableY = getYOnArc( 41674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 417896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 418896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 419896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleLeft); 420896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 42174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 4225037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, dimpleLeft, drawableY + bgTop); 42374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 4245037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleLeft); 42574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 426896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleLeft -= mDimpleSpacing; 427896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 428896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 429896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // draw extra right hand dimples 430896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen int dimpleRight = mRotaryOffsetX + mRightHandleX + mDimpleSpacing; 431896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int rightThresh = mRight + halfdimple; 432896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen while (dimpleRight < rightThresh) { 433896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int drawableY = getYOnArc( 43474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 435896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 436896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 437896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleRight); 438896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 43974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 4405037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, dimpleRight, drawableY + bgTop); 44174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 4425037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleRight); 44374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 444896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleRight += mDimpleSpacing; 445896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 446e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 447e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 448e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 44974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * Assuming bitmap is a bounding box around a piece of an arc drawn by two concentric circles 450e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * (as the background drawable for the rotary widget is), and given an x coordinate along the 451e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * drawable, return the y coordinate of a point on the arc that is between the two concentric 452e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * circles. The resulting y combined with the incoming x is a point along the circle in 453e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * between the two concentric circles. 454e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 45574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * @param backgroundWidth The width of the asset (the bottom of the box surrounding the arc). 456e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param innerRadius The radius of the circle that intersects the drawable at the bottom two 457e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * corders of the drawable (top two corners in terms of drawing coordinates). 458e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param outerRadius The radius of the circle who's top most point is the top center of the 459e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * drawable (bottom center in terms of drawing coordinates). 46074646ad618a9ca289efa99b4a822e66ca61b8f95Karl 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 461e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * in between the two concentric circles. 462e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 46374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private int getYOnArc(int backgroundWidth, int innerRadius, int outerRadius, int x) { 464e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 465e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the hypotenuse 466e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int halfWidth = (outerRadius - innerRadius) / 2; 467e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int middleRadius = innerRadius + halfWidth; 468e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 469e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the bottom leg of the triangle 47074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int triangleBottom = (backgroundWidth / 2) - x; 471e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 472e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // "Our offense is like the pythagorean theorem: There is no answer!" - Shaquille O'Neal 473e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int triangleY = 474e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen (int) Math.sqrt(middleRadius * middleRadius - triangleBottom * triangleBottom); 475e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 476e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // convert to drawing coordinates: 477e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // middleRadius - triangleY = 478e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the vertical distance from the outer edge of the circle to the desired point 479e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // from there we add the distance from the top of the drawable to the middle circle 480e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return middleRadius - triangleY + halfWidth; 481e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 482e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 483e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 484e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Handle touch screen events. 485e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 486e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param event The motion event. 487e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @return True if the event was handled, false otherwise. 488e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 489e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 490e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public boolean onTouchEvent(MotionEvent event) { 491278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen if (mAnimating) { 492e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return true; 493e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 494896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker == null) { 495896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = VelocityTracker.obtain(); 496896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 497896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.addMovement(event); 498896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 49974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int height = getHeight(); 500e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 50174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int eventX = isHoriz() ? 50274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) event.getX(): 50374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen height - ((int) event.getY()); 504278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen final int hitWindow = mDimpleWidth; 505e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 5061ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen final int action = event.getAction(); 5071ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen switch (action) { 5081ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_DOWN: 5091ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-down"); 5101ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = false; 5111ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState != NOTHING_GRABBED) { 5121ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen reset(); 5131ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 514e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 5151ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (eventX < mLeftHandleX + hitWindow) { 516896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mLeftHandleX; 51788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(LEFT_HANDLE_GRABBED); 5181ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5191ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen vibrate(VIBRATE_SHORT); 5201ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (eventX > mRightHandleX - hitWindow) { 521896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mRightHandleX; 52288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(RIGHT_HANDLE_GRABBED); 5231ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5241ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen vibrate(VIBRATE_SHORT); 525e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 5261ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 527e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 5281ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_MOVE: 5291ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-move"); 5301ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState == LEFT_HANDLE_GRABBED) { 531896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mLeftHandleX; 5321ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 53374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rightThresh = isHoriz() ? getRight() : height; 53474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (eventX >= rightThresh - mEdgeTriggerThresh && !mTriggered) { 5351ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = true; 536278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen dispatchTriggerEvent(OnDialTriggerListener.LEFT_HANDLE); 537896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final VelocityTracker velocityTracker = mVelocityTracker; 538896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 53974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rawVelocity = isHoriz() ? 54074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) velocityTracker.getXVelocity(): 54174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen -(int) velocityTracker.getYVelocity(); 54274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int velocity = Math.max(mMinimumVelocity, rawVelocity); 543896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = Math.max( 544896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 8, 545896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen Math.abs(velocity / mDimpleSpacing)); 546896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimationWithVelocity( 547896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen eventX - mLeftHandleX, 548896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling * mDimpleSpacing, 549896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocity); 5501ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5511ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (mGrabbedState == RIGHT_HANDLE_GRABBED) { 552896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mRightHandleX; 5531ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5545fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen if (eventX <= mEdgeTriggerThresh && !mTriggered) { 5551ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = true; 556278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen dispatchTriggerEvent(OnDialTriggerListener.RIGHT_HANDLE); 557896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final VelocityTracker velocityTracker = mVelocityTracker; 558896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 55974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rawVelocity = isHoriz() ? 56074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) velocityTracker.getXVelocity(): 56174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen - (int) velocityTracker.getYVelocity(); 56274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int velocity = Math.min(-mMinimumVelocity, rawVelocity); 563896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = Math.max( 564896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 8, 565896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen Math.abs(velocity / mDimpleSpacing)); 566896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimationWithVelocity( 567896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen eventX - mRightHandleX, 568896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen -(mDimplesOfFling * mDimpleSpacing), 569896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocity); 5701ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5711ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5721ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 5731ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_UP: 5741ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-up"); 5751ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen // handle animating back to start if they didn't trigger 5761ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState == LEFT_HANDLE_GRABBED 5771ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen && Math.abs(eventX - mLeftHandleX) > 5) { 578278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen // set up "snap back" animation 579896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimation(eventX - mLeftHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); 5801ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (mGrabbedState == RIGHT_HANDLE_GRABBED 5811ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen && Math.abs(eventX - mRightHandleX) > 5) { 582278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen // set up "snap back" animation 583896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimation(eventX - mRightHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); 5841ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 585896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = 0; 58688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 5871ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 588896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker != null) { 589896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.recycle(); // wishin' we had generational GC 590896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = null; 591896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 5921ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 5931ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_CANCEL: 5941ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-cancel"); 5951ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen reset(); 5961ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 597896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker != null) { 598896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.recycle(); 599896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = null; 600896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 6011ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 602e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 603e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return true; 604e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 605e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 606896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void startAnimation(int startX, int endX, int duration) { 607896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimating = true; 608896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationStartTime = currentAnimationTimeMillis(); 609896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationDuration = duration; 610896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXStart = startX; 611896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXEnd = endX; 61288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 613896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = 0; 614896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 615896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 616896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 617896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void startAnimationWithVelocity(int startX, int endX, int pixelsPerSecond) { 618896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimating = true; 619896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationStartTime = currentAnimationTimeMillis(); 620896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationDuration = 1000 * (endX - startX) / pixelsPerSecond; 621896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXStart = startX; 622896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXEnd = endX; 62388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 624896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 625896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 626896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 627896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void updateAnimation() { 628896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final long millisSoFar = currentAnimationTimeMillis() - mAnimationStartTime; 629896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final long millisLeft = mAnimationDuration - millisSoFar; 630896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int totalDeltaX = mAnimatingDeltaXStart - mAnimatingDeltaXEnd; 63174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final boolean goingRight = totalDeltaX < 0; 632896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (DBG) log("millisleft for animating: " + millisLeft); 633896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (millisLeft <= 0) { 634896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen reset(); 635896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen return; 636896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 637896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // from 0 to 1 as animation progresses 638896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen float interpolation = 639896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInterpolator.getInterpolation((float) millisSoFar / mAnimationDuration); 640896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int dx = (int) (totalDeltaX * (1 - interpolation)); 641896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = mAnimatingDeltaXEnd + dx; 64274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 64374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // once we have gone far enough to animate the current buttons off screen, we start 64474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // wrapping the offset back to the other side so that when the animation is finished, 64574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // the buttons will come back into their original places. 646896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mDimplesOfFling > 0) { 647ff9c54b7214e99b3182f53f3089da0503f3edddcKarl Rosaen if (!goingRight && mRotaryOffsetX < -3 * mDimpleSpacing) { 648896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // wrap around on fling left 64974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRotaryOffsetX += mDimplesOfFling * mDimpleSpacing; 65074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else if (goingRight && mRotaryOffsetX > 3 * mDimpleSpacing) { 651896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // wrap around on fling right 65274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRotaryOffsetX -= mDimplesOfFling * mDimpleSpacing; 653896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 654896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 655896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 656896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 657896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 658e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private void reset() { 659e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mAnimating = false; 660896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = 0; 661896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = 0; 66288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 663e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mTriggered = false; 664e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 665e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 666e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 667e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Triggers haptic feedback. 668e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 669e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private synchronized void vibrate(long duration) { 670e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (mVibrator == null) { 671896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVibrator = (android.os.Vibrator) 672896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen getContext().getSystemService(Context.VIBRATOR_SERVICE); 673e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 674e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mVibrator.vibrate(duration); 675e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 676e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 677e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 678c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen * Draw the bitmap so that it's centered 679c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen * on the point (x,y), then draws it using specified canvas. 680e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * TODO: is there already a utility method somewhere for this? 681e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 682c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private void drawCentered(Bitmap d, Canvas c, int x, int y) { 683c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen int w = d.getWidth(); 684c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen int h = d.getHeight(); 685c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen 686c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen c.drawBitmap(d, x - (w / 2), y - (h / 2), mPaint); 687e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 688e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 689e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 690e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 691e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Registers a callback to be invoked when the dial 692e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * is "triggered" by rotating it one way or the other. 693e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 694e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param l the OnDialTriggerListener to attach to this view 695e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 696e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setOnDialTriggerListener(OnDialTriggerListener l) { 697e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mOnDialTriggerListener = l; 698e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 699e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 700e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 701e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Dispatches a trigger event to our listener. 702e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 703278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private void dispatchTriggerEvent(int whichHandle) { 704e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen vibrate(VIBRATE_LONG); 705e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (mOnDialTriggerListener != null) { 706278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen mOnDialTriggerListener.onDialTrigger(this, whichHandle); 707e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 708e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 709e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 710e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 71188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * Sets the current grabbed state, and dispatches a grabbed state change 71288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * event to our listener. 71388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown */ 71488e037577f7db140e4ef88b77eefaa910e06e5f5David Brown private void setGrabbedState(int newState) { 71588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown if (newState != mGrabbedState) { 71688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown mGrabbedState = newState; 71788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown if (mOnDialTriggerListener != null) { 71888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown mOnDialTriggerListener.onGrabbedStateChange(this, mGrabbedState); 71988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 72088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 72188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 72288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown 72388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown /** 724e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Interface definition for a callback to be invoked when the dial 725e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * is "triggered" by rotating it one way or the other. 726e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 727e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public interface OnDialTriggerListener { 728e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 729e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The dial was triggered because the user grabbed the left handle, 730e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * and rotated the dial clockwise. 731e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 732e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public static final int LEFT_HANDLE = 1; 733e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 734e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 735e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The dial was triggered because the user grabbed the right handle, 736e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * and rotated the dial counterclockwise. 737e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 738e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public static final int RIGHT_HANDLE = 2; 739e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 740e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 741e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Called when the dial is triggered. 742e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 743e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param v The view that was triggered 744e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param whichHandle Which "dial handle" the user grabbed, 745278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen * either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}. 746e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 747278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen void onDialTrigger(View v, int whichHandle); 74888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown 74988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown /** 75088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * Called when the "grabbed state" changes (i.e. when 75188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * the user either grabs or releases one of the handles.) 75288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * 75388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * @param v the view that was triggered 75488e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * @param grabbedState the new state: either {@link #NOTHING_GRABBED}, 75588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * {@link #LEFT_HANDLE_GRABBED}, or {@link #RIGHT_HANDLE_GRABBED}. 75688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown */ 75788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown void onGrabbedStateChange(View v, int grabbedState); 758e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 759e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 760e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 761e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Debugging / testing code 762e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 763e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private void log(String msg) { 764e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen Log.d(LOG_TAG, msg); 765e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 766e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen} 767