RotarySelector.java revision f9e1a0b369740e11ea1ed4f141ffb936fc1a6cdb
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; 27f9e1a0b369740e11ea1ed4f141ffb936fc1a6cdbJohn Spurlockimport android.media.AudioManager; 28723a725e790d269f32980116e775d3d7f0037865Jeff Sharkeyimport android.os.UserHandle; 29e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.os.Vibrator; 30723a725e790d269f32980116e775d3d7f0037865Jeff Sharkeyimport android.provider.Settings; 31e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.util.AttributeSet; 32e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.util.Log; 33e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.view.MotionEvent; 34e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.view.View; 35896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaenimport android.view.VelocityTracker; 36896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaenimport android.view.ViewConfiguration; 37896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaenimport android.view.animation.DecelerateInterpolator; 38f9e1a0b369740e11ea1ed4f141ffb936fc1a6cdbJohn Spurlock 391ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaenimport static android.view.animation.AnimationUtils.currentAnimationTimeMillis; 40f9e1a0b369740e11ea1ed4f141ffb936fc1a6cdbJohn Spurlock 41e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport com.android.internal.R; 42e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 43e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 44e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen/** 45e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Custom view that presents up to two items that are selectable by rotating a semi-circle from 46e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * left to right, or right to left. Used by incoming call screen, and the lock screen when no 47e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * security pattern is set. 48e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 49e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenpublic class RotarySelector extends View { 5074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen public static final int HORIZONTAL = 0; 5174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen public static final int VERTICAL = 1; 5274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 53e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final String LOG_TAG = "RotarySelector"; 54e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final boolean DBG = false; 555037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller private static final boolean VISUAL_DEBUG = false; 56e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 57e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Listener for onDialTrigger() callbacks. 58e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private OnDialTriggerListener mOnDialTriggerListener; 59e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 60e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private float mDensity; 61e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 62e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // UI elements 6374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mBackground; 64c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mDimple; 65d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller private Bitmap mDimpleDim; 66e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 67c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mLeftHandleIcon; 68c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mRightHandleIcon; 69e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 7074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowShortLeftAndRight; 7174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowLongLeft; // Long arrow starting on the left, pointing clockwise 7274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowLongRight; // Long arrow starting on the right, pointing CCW 73e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 74e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // positions of the left and right handle 75e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mLeftHandleX; 76e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mRightHandleX; 77e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 78896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // current offset of rotary widget along the x axis 79896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mRotaryOffsetX = 0; 80e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 81e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // state of the animation used to bring the handle back to its start position when 82e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the user lets go before triggering an action 83e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private boolean mAnimating = false; 84896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private long mAnimationStartTime; 85052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen private long mAnimationDuration; 86896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mAnimatingDeltaXStart; // the animation will interpolate from this delta to zero 87896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mAnimatingDeltaXEnd; 88896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 89896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private DecelerateInterpolator mInterpolator; 90e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 9174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Paint mPaint = new Paint(); 9274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 9374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // used to rotate the background and arrow assets depending on orientation 9474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final Matrix mBgMatrix = new Matrix(); 9574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final Matrix mArrowMatrix = new Matrix(); 9674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 97e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 98e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * If the user is currently dragging something. 99e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 100e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mGrabbedState = NOTHING_GRABBED; 10188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int NOTHING_GRABBED = 0; 10288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int LEFT_HANDLE_GRABBED = 1; 10388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int RIGHT_HANDLE_GRABBED = 2; 104e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 105e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 106e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Whether the user has triggered something (e.g dragging the left handle all the way over to 107e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the right). 108e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 109e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private boolean mTriggered = false; 110e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 111e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Vibration (haptic feedback) 112e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private Vibrator mVibrator; 1139485aec1df6094b7d2497212c8fe04a6d459b4cdJim Miller private static final long VIBRATE_SHORT = 20; // msec 1149485aec1df6094b7d2497212c8fe04a6d459b4cdJim Miller private static final long VIBRATE_LONG = 20; // msec 115e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 116e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 117e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The drawable for the arrows need to be scrunched this many dips towards the rotary bg below 118e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * it. 119e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 120e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final int ARROW_SCRUNCH_DIP = 6; 121e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 122e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 123e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * How far inset the left and right circles should be 124e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 125e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final int EDGE_PADDING_DIP = 9; 126e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 127e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 1285fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen * How far from the edge of the screen the user must drag to trigger the event. 1295fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen */ 130052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen private static final int EDGE_TRIGGER_DIP = 100; 1315fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen 1325fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen /** 133e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Dimensions of arc in background drawable. 134e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 135e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen static final int OUTER_ROTARY_RADIUS_DIP = 390; 136e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen static final int ROTARY_STROKE_WIDTH_DIP = 83; 137052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen static final int SNAP_BACK_ANIMATION_DURATION_MILLIS = 300; 138052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen static final int SPIN_ANIMATION_DURATION_MILLIS = 800; 139e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 1405fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen private int mEdgeTriggerThresh; 141278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mDimpleWidth; 142278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mBackgroundWidth; 143278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mBackgroundHeight; 144896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private final int mOuterRadius; 145896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private final int mInnerRadius; 146896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mDimpleSpacing; 147896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 148896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private VelocityTracker mVelocityTracker; 149896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mMinimumVelocity; 150896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mMaximumVelocity; 151896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 152896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen /** 153896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * The number of dimples we are flinging when we do the "spin" animation. Used to know when to 154896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * wrap the icons back around so they "rotate back" onto the screen. 155896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * @see #updateAnimation() 156896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen */ 157896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mDimplesOfFling = 0; 158896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 15974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen /** 16074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * Either {@link #HORIZONTAL} or {@link #VERTICAL}. 16174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen */ 16274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private int mOrientation; 163896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 164e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 1651ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen public RotarySelector(Context context) { 1661ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen this(context, null); 1671ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 1681ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen 169e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 170e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Constructor used when this widget is created from a layout file. 171e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 172e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public RotarySelector(Context context, AttributeSet attrs) { 173e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen super(context, attrs); 17474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 17574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen TypedArray a = 17674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen context.obtainStyledAttributes(attrs, R.styleable.RotarySelector); 17774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mOrientation = a.getInt(R.styleable.RotarySelector_orientation, HORIZONTAL); 17874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen a.recycle(); 179e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 180e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen Resources r = getResources(); 181e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mDensity = r.getDisplayMetrics().density; 182e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (DBG) log("- Density: " + mDensity); 183e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 184e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Assets (all are BitmapDrawables). 18574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackground = getBitmapFor(R.drawable.jog_dial_bg); 186c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mDimple = getBitmapFor(R.drawable.jog_dial_dimple); 187d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller mDimpleDim = getBitmapFor(R.drawable.jog_dial_dimple_dim); 188e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 18974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowLongLeft = getBitmapFor(R.drawable.jog_dial_arrow_long_left_green); 19074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowLongRight = getBitmapFor(R.drawable.jog_dial_arrow_long_right_red); 19174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowShortLeftAndRight = getBitmapFor(R.drawable.jog_dial_arrow_short_left_and_right); 1921ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen 193896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInterpolator = new DecelerateInterpolator(1f); 1945fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen 1955fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen mEdgeTriggerThresh = (int) (mDensity * EDGE_TRIGGER_DIP); 196278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen 197c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mDimpleWidth = mDimple.getWidth(); 198278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen 19974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth = mBackground.getWidth(); 20074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundHeight = mBackground.getHeight(); 201896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius = (int) (mDensity * OUTER_ROTARY_RADIUS_DIP); 202896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius = (int) ((OUTER_ROTARY_RADIUS_DIP - ROTARY_STROKE_WIDTH_DIP) * mDensity); 203896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 204896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final ViewConfiguration configuration = ViewConfiguration.get(mContext); 205896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mMinimumVelocity = configuration.getScaledMinimumFlingVelocity() * 2; 206896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 207896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 208896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 20974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap getBitmapFor(int resId) { 21074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen return BitmapFactory.decodeResource(getContext().getResources(), resId); 21174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 21274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 213896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen @Override 214896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen protected void onSizeChanged(int w, int h, int oldw, int oldh) { 215896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen super.onSizeChanged(w, h, oldw, oldh); 216896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 21774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int edgePadding = (int) (EDGE_PADDING_DIP * mDensity); 21874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mLeftHandleX = edgePadding + mDimpleWidth / 2; 21974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int length = isHoriz() ? w : h; 22074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRightHandleX = length - edgePadding - mDimpleWidth / 2; 22174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mDimpleSpacing = (length / 2) - mLeftHandleX; 22274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 22374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // bg matrix only needs to be calculated once 22474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.setTranslate(0, 0); 22574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 22674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // set up matrix for translating drawing of background and arrow assets 22774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int left = w - mBackgroundHeight; 22874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.preRotate(-90, 0, 0); 22974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.postTranslate(left, h); 23074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 23174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 23274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.postTranslate(0, h - mBackgroundHeight); 23374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 23474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 235896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 23674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private boolean isHoriz() { 23774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen return mOrientation == HORIZONTAL; 238e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 239e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 240e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 241e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Sets the left handle icon to a given resource. 242e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 243e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The resource should refer to a Drawable object, or use 0 to remove 244e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the icon. 245e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 246e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param resId the resource ID. 247e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 248e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setLeftHandleResource(int resId) { 249e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (resId != 0) { 250c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mLeftHandleIcon = getBitmapFor(resId); 251e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 252e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen invalidate(); 253e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 254e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 255e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 256e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Sets the right handle icon to a given resource. 257e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 258e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The resource should refer to a Drawable object, or use 0 to remove 259e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the icon. 260e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 261e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param resId the resource ID. 262e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 263e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setRightHandleResource(int resId) { 264e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (resId != 0) { 265c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mRightHandleIcon = getBitmapFor(resId); 266e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 267e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen invalidate(); 268e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 269e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 270c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen 271e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 272e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 27374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int length = isHoriz() ? 27474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen MeasureSpec.getSize(widthMeasureSpec) : 27574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen MeasureSpec.getSize(heightMeasureSpec); 27674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int arrowScrunch = (int) (ARROW_SCRUNCH_DIP * mDensity); 27774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int arrowH = mArrowShortLeftAndRight.getHeight(); 278e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 279e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // by making the height less than arrow + bg, arrow and bg will be scrunched together, 280e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // overlaying somewhat (though on transparent portions of the drawable). 281e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // this works because the arrows are drawn from the top, and the rotary bg is drawn 282e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // from the bottom. 28374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int height = mBackgroundHeight + arrowH - arrowScrunch; 284e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 28574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 28674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen setMeasuredDimension(length, height); 28774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 28874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen setMeasuredDimension(height, length); 28974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 29074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 291e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 292e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 293e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen protected void onDraw(Canvas canvas) { 294e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen super.onDraw(canvas); 29574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 29674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int width = getWidth(); 29774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 2985037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (VISUAL_DEBUG) { 2995037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller // draw bounding box around widget 3005037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller mPaint.setColor(0xffff0000); 3015037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller mPaint.setStyle(Paint.Style.STROKE); 3025037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller canvas.drawRect(0, 0, width, getHeight(), mPaint); 3035037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } 304e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 305e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int height = getHeight(); 306e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 307e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // update animating state before we draw anything 308278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen if (mAnimating) { 309896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen updateAnimation(); 310e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 311e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 312e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Background: 31374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mBackground, mBgMatrix, mPaint); 314e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 315e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Draw the correct arrow(s) depending on the current state: 31674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.reset(); 317e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen switch (mGrabbedState) { 318e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case NOTHING_GRABBED: 31974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen //mArrowShortLeftAndRight; 320e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 321e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case LEFT_HANDLE_GRABBED: 32274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.setTranslate(0, 0); 32374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 32474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.preRotate(-90, 0, 0); 32574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.postTranslate(0, height); 32674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 32774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mArrowLongLeft, mArrowMatrix, mPaint); 328e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 329e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case RIGHT_HANDLE_GRABBED: 33074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.setTranslate(0, 0); 33174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 33274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.preRotate(-90, 0, 0); 33374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // since bg width is > height of screen in landscape mode... 33474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.postTranslate(0, height + (mBackgroundWidth - height)); 33574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 33674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mArrowLongRight, mArrowMatrix, mPaint); 337e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 338e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen default: 339e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen throw new IllegalStateException("invalid mGrabbedState: " + mGrabbedState); 340e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 341e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 34274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int bgHeight = mBackgroundHeight; 34374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int bgTop = isHoriz() ? 34474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen height - bgHeight: 34574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen width - bgHeight; 3465037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller 3475037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (VISUAL_DEBUG) { 3485037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller // draw circle bounding arc drawable: good sanity check we're doing the math correctly 3495037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller float or = OUTER_ROTARY_RADIUS_DIP * mDensity; 3505037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int vOffset = mBackgroundWidth - height; 3515037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int midX = isHoriz() ? width / 2 : mBackgroundWidth / 2 - vOffset; 3525037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (isHoriz()) { 3535037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller canvas.drawCircle(midX, or + bgTop, or, mPaint); 3545037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } else { 3555037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller canvas.drawCircle(or + bgTop, midX, or, mPaint); 3565037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } 3575037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } 358d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller 35974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // left dimple / icon 360e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen { 361896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int xOffset = mLeftHandleX + mRotaryOffsetX; 362e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int drawableY = getYOnArc( 36374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 364896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 365896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 366e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen xOffset); 3675037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int x = isHoriz() ? xOffset : drawableY + bgTop; 3685037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int y = isHoriz() ? drawableY + bgTop : height - xOffset; 3695037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (mGrabbedState != RIGHT_HANDLE_GRABBED) { 3705037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimple, canvas, x, y); 3715037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mLeftHandleIcon, canvas, x, y); 37274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 3735037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, x, y); 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()) { 3895037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, xOffset, drawableY + bgTop); 39074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 39174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // vertical 3925037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, 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 4055037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int x = isHoriz() ? xOffset : drawableY + bgTop; 4065037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int y = isHoriz() ? drawableY + bgTop : height - xOffset; 4075037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (mGrabbedState != LEFT_HANDLE_GRABBED) { 4085037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimple, canvas, x, y); 4095037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mRightHandleIcon, canvas, x, y); 41074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 4115037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, x, y); 4125fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen } 413e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 414e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 415896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // draw extra left hand dimples 416896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen int dimpleLeft = mRotaryOffsetX + mLeftHandleX - mDimpleSpacing; 417896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int halfdimple = mDimpleWidth / 2; 418896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen while (dimpleLeft > -halfdimple) { 419896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int drawableY = getYOnArc( 42074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 421896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 422896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 423896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleLeft); 424896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 42574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 4265037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, dimpleLeft, drawableY + bgTop); 42774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 4285037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleLeft); 42974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 430896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleLeft -= mDimpleSpacing; 431896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 432896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 433896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // draw extra right hand dimples 434896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen int dimpleRight = mRotaryOffsetX + mRightHandleX + mDimpleSpacing; 435896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int rightThresh = mRight + halfdimple; 436896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen while (dimpleRight < rightThresh) { 437896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int drawableY = getYOnArc( 43874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 439896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 440896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 441896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleRight); 442896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 44374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 4445037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, dimpleRight, drawableY + bgTop); 44574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 4465037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleRight); 44774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 448896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleRight += mDimpleSpacing; 449896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 450e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 451e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 452e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 45374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * Assuming bitmap is a bounding box around a piece of an arc drawn by two concentric circles 454e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * (as the background drawable for the rotary widget is), and given an x coordinate along the 455e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * drawable, return the y coordinate of a point on the arc that is between the two concentric 456e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * circles. The resulting y combined with the incoming x is a point along the circle in 457e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * between the two concentric circles. 458e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 45974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * @param backgroundWidth The width of the asset (the bottom of the box surrounding the arc). 460e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param innerRadius The radius of the circle that intersects the drawable at the bottom two 461e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * corders of the drawable (top two corners in terms of drawing coordinates). 462e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param outerRadius The radius of the circle who's top most point is the top center of the 463e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * drawable (bottom center in terms of drawing coordinates). 46474646ad618a9ca289efa99b4a822e66ca61b8f95Karl 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 465e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * in between the two concentric circles. 466e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 46774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private int getYOnArc(int backgroundWidth, int innerRadius, int outerRadius, int x) { 468e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 469e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the hypotenuse 470e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int halfWidth = (outerRadius - innerRadius) / 2; 471e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int middleRadius = innerRadius + halfWidth; 472e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 473e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the bottom leg of the triangle 47474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int triangleBottom = (backgroundWidth / 2) - x; 475e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 476e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // "Our offense is like the pythagorean theorem: There is no answer!" - Shaquille O'Neal 477e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int triangleY = 478e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen (int) Math.sqrt(middleRadius * middleRadius - triangleBottom * triangleBottom); 479e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 480e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // convert to drawing coordinates: 481e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // middleRadius - triangleY = 482e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the vertical distance from the outer edge of the circle to the desired point 483e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // from there we add the distance from the top of the drawable to the middle circle 484e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return middleRadius - triangleY + halfWidth; 485e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 486e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 487e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 488e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Handle touch screen events. 489e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 490e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param event The motion event. 491e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @return True if the event was handled, false otherwise. 492e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 493e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 494e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public boolean onTouchEvent(MotionEvent event) { 495278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen if (mAnimating) { 496e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return true; 497e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 498896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker == null) { 499896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = VelocityTracker.obtain(); 500896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 501896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.addMovement(event); 502896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 50374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int height = getHeight(); 504e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 50574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int eventX = isHoriz() ? 50674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) event.getX(): 50774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen height - ((int) event.getY()); 508278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen final int hitWindow = mDimpleWidth; 509e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 5101ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen final int action = event.getAction(); 5111ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen switch (action) { 5121ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_DOWN: 5131ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-down"); 5141ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = false; 5151ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState != NOTHING_GRABBED) { 5161ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen reset(); 5171ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 518e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 5191ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (eventX < mLeftHandleX + hitWindow) { 520896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mLeftHandleX; 52188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(LEFT_HANDLE_GRABBED); 5221ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5231ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen vibrate(VIBRATE_SHORT); 5241ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (eventX > mRightHandleX - hitWindow) { 525896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mRightHandleX; 52688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(RIGHT_HANDLE_GRABBED); 5271ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5281ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen vibrate(VIBRATE_SHORT); 529e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 5301ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 531e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 5321ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_MOVE: 5331ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-move"); 5341ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState == LEFT_HANDLE_GRABBED) { 535896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mLeftHandleX; 5361ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 53774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rightThresh = isHoriz() ? getRight() : height; 53874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (eventX >= rightThresh - mEdgeTriggerThresh && !mTriggered) { 5391ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = true; 540278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen dispatchTriggerEvent(OnDialTriggerListener.LEFT_HANDLE); 541896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final VelocityTracker velocityTracker = mVelocityTracker; 542896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 54374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rawVelocity = isHoriz() ? 54474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) velocityTracker.getXVelocity(): 54574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen -(int) velocityTracker.getYVelocity(); 54674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int velocity = Math.max(mMinimumVelocity, rawVelocity); 547896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = Math.max( 548896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 8, 549896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen Math.abs(velocity / mDimpleSpacing)); 550896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimationWithVelocity( 551896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen eventX - mLeftHandleX, 552896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling * mDimpleSpacing, 553896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocity); 5541ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5551ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (mGrabbedState == RIGHT_HANDLE_GRABBED) { 556896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mRightHandleX; 5571ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5585fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen if (eventX <= mEdgeTriggerThresh && !mTriggered) { 5591ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = true; 560278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen dispatchTriggerEvent(OnDialTriggerListener.RIGHT_HANDLE); 561896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final VelocityTracker velocityTracker = mVelocityTracker; 562896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 56374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rawVelocity = isHoriz() ? 56474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) velocityTracker.getXVelocity(): 56574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen - (int) velocityTracker.getYVelocity(); 56674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int velocity = Math.min(-mMinimumVelocity, rawVelocity); 567896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = Math.max( 568896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 8, 569896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen Math.abs(velocity / mDimpleSpacing)); 570896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimationWithVelocity( 571896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen eventX - mRightHandleX, 572896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen -(mDimplesOfFling * mDimpleSpacing), 573896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocity); 5741ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5751ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5761ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 5771ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_UP: 5781ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-up"); 5791ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen // handle animating back to start if they didn't trigger 5801ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState == LEFT_HANDLE_GRABBED 5811ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen && Math.abs(eventX - mLeftHandleX) > 5) { 582278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen // set up "snap back" animation 583896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimation(eventX - mLeftHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); 5841ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (mGrabbedState == RIGHT_HANDLE_GRABBED 5851ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen && Math.abs(eventX - mRightHandleX) > 5) { 586278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen // set up "snap back" animation 587896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimation(eventX - mRightHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); 5881ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 589896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = 0; 59088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 5911ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 592896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker != null) { 593896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.recycle(); // wishin' we had generational GC 594896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = null; 595896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 5961ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 5971ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_CANCEL: 5981ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-cancel"); 5991ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen reset(); 6001ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 601896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker != null) { 602896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.recycle(); 603896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = null; 604896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 6051ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 606e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 607e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return true; 608e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 609e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 610896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void startAnimation(int startX, int endX, int duration) { 611896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimating = true; 612896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationStartTime = currentAnimationTimeMillis(); 613896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationDuration = duration; 614896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXStart = startX; 615896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXEnd = endX; 61688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 617896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = 0; 618896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 619896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 620896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 621896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void startAnimationWithVelocity(int startX, int endX, int pixelsPerSecond) { 622896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimating = true; 623896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationStartTime = currentAnimationTimeMillis(); 624896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationDuration = 1000 * (endX - startX) / pixelsPerSecond; 625896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXStart = startX; 626896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXEnd = endX; 62788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 628896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 629896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 630896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 631896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void updateAnimation() { 632896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final long millisSoFar = currentAnimationTimeMillis() - mAnimationStartTime; 633896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final long millisLeft = mAnimationDuration - millisSoFar; 634896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int totalDeltaX = mAnimatingDeltaXStart - mAnimatingDeltaXEnd; 63574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final boolean goingRight = totalDeltaX < 0; 636896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (DBG) log("millisleft for animating: " + millisLeft); 637896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (millisLeft <= 0) { 638896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen reset(); 639896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen return; 640896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 641896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // from 0 to 1 as animation progresses 642896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen float interpolation = 643896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInterpolator.getInterpolation((float) millisSoFar / mAnimationDuration); 644896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int dx = (int) (totalDeltaX * (1 - interpolation)); 645896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = mAnimatingDeltaXEnd + dx; 64674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 64774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // once we have gone far enough to animate the current buttons off screen, we start 64874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // wrapping the offset back to the other side so that when the animation is finished, 64974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // the buttons will come back into their original places. 650896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mDimplesOfFling > 0) { 651ff9c54b7214e99b3182f53f3089da0503f3edddcKarl Rosaen if (!goingRight && mRotaryOffsetX < -3 * mDimpleSpacing) { 652896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // wrap around on fling left 65374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRotaryOffsetX += mDimplesOfFling * mDimpleSpacing; 65474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else if (goingRight && mRotaryOffsetX > 3 * mDimpleSpacing) { 655896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // wrap around on fling right 65674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRotaryOffsetX -= mDimplesOfFling * mDimpleSpacing; 657896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 658896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 659896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 660896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 661896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 662e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private void reset() { 663e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mAnimating = false; 664896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = 0; 665896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = 0; 66688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 667e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mTriggered = false; 668e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 669e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 670e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 671e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Triggers haptic feedback. 672e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 673e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private synchronized void vibrate(long duration) { 674723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey final boolean hapticEnabled = Settings.System.getIntForUser( 675723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, 676723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey UserHandle.USER_CURRENT) != 0; 677723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey if (hapticEnabled) { 678723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey if (mVibrator == null) { 679723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey mVibrator = (android.os.Vibrator) getContext() 680723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey .getSystemService(Context.VIBRATOR_SERVICE); 681723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey } 682f9e1a0b369740e11ea1ed4f141ffb936fc1a6cdbJohn Spurlock mVibrator.vibrate(duration, AudioManager.STREAM_SYSTEM); 683e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 684e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 685e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 686e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 687c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen * Draw the bitmap so that it's centered 688c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen * on the point (x,y), then draws it using specified canvas. 689e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * TODO: is there already a utility method somewhere for this? 690e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 691c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private void drawCentered(Bitmap d, Canvas c, int x, int y) { 692c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen int w = d.getWidth(); 693c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen int h = d.getHeight(); 694c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen 695c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen c.drawBitmap(d, x - (w / 2), y - (h / 2), mPaint); 696e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 697e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 698e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 699e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 700e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Registers a callback to be invoked when the dial 701e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * is "triggered" by rotating it one way or the other. 702e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 703e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param l the OnDialTriggerListener to attach to this view 704e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 705e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setOnDialTriggerListener(OnDialTriggerListener l) { 706e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mOnDialTriggerListener = l; 707e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 708e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 709e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 710e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Dispatches a trigger event to our listener. 711e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 712278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private void dispatchTriggerEvent(int whichHandle) { 713e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen vibrate(VIBRATE_LONG); 714e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (mOnDialTriggerListener != null) { 715278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen mOnDialTriggerListener.onDialTrigger(this, whichHandle); 716e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 717e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 718e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 719e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 72088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * Sets the current grabbed state, and dispatches a grabbed state change 72188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * event to our listener. 72288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown */ 72388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown private void setGrabbedState(int newState) { 72488e037577f7db140e4ef88b77eefaa910e06e5f5David Brown if (newState != mGrabbedState) { 72588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown mGrabbedState = newState; 72688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown if (mOnDialTriggerListener != null) { 72788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown mOnDialTriggerListener.onGrabbedStateChange(this, mGrabbedState); 72888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 72988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 73088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 73188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown 73288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown /** 733e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Interface definition for a callback to be invoked when the dial 734e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * is "triggered" by rotating it one way or the other. 735e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 736e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public interface OnDialTriggerListener { 737e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 738e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The dial was triggered because the user grabbed the left handle, 739e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * and rotated the dial clockwise. 740e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 741e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public static final int LEFT_HANDLE = 1; 742e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 743e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 744e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The dial was triggered because the user grabbed the right handle, 745e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * and rotated the dial counterclockwise. 746e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 747e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public static final int RIGHT_HANDLE = 2; 748e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 749e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 750e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Called when the dial is triggered. 751e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 752e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param v The view that was triggered 753e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param whichHandle Which "dial handle" the user grabbed, 754278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen * either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}. 755e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 756278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen void onDialTrigger(View v, int whichHandle); 75788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown 75888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown /** 75988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * Called when the "grabbed state" changes (i.e. when 76088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * the user either grabs or releases one of the handles.) 76188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * 76288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * @param v the view that was triggered 76388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * @param grabbedState the new state: either {@link #NOTHING_GRABBED}, 76488e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * {@link #LEFT_HANDLE_GRABBED}, or {@link #RIGHT_HANDLE_GRABBED}. 76588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown */ 76688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown void onGrabbedStateChange(View v, int grabbedState); 767e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 768e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 769e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 770e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Debugging / testing code 771e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 772e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private void log(String msg) { 773e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen Log.d(LOG_TAG, msg); 774e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 775e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen} 776