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; 277b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlockimport android.media.AudioAttributes; 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 577b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() 587b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 597b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) 607b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock .build(); 617b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock 62e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Listener for onDialTrigger() callbacks. 63e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private OnDialTriggerListener mOnDialTriggerListener; 64e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 65e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private float mDensity; 66e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 67e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // UI elements 6874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mBackground; 69c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mDimple; 70d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller private Bitmap mDimpleDim; 71e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 72c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mLeftHandleIcon; 73c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private Bitmap mRightHandleIcon; 74e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 7574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowShortLeftAndRight; 7674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowLongLeft; // Long arrow starting on the left, pointing clockwise 7774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap mArrowLongRight; // Long arrow starting on the right, pointing CCW 78e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 79e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // positions of the left and right handle 80e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mLeftHandleX; 81e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mRightHandleX; 82e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 83896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // current offset of rotary widget along the x axis 84896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mRotaryOffsetX = 0; 85e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 86e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // state of the animation used to bring the handle back to its start position when 87e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the user lets go before triggering an action 88e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private boolean mAnimating = false; 89896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private long mAnimationStartTime; 90052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen private long mAnimationDuration; 91896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mAnimatingDeltaXStart; // the animation will interpolate from this delta to zero 92896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mAnimatingDeltaXEnd; 93896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 94896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private DecelerateInterpolator mInterpolator; 95e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 9674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Paint mPaint = new Paint(); 9774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 9874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // used to rotate the background and arrow assets depending on orientation 9974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final Matrix mBgMatrix = new Matrix(); 10074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final Matrix mArrowMatrix = new Matrix(); 10174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 102e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 103e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * If the user is currently dragging something. 104e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 105e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private int mGrabbedState = NOTHING_GRABBED; 10688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int NOTHING_GRABBED = 0; 10788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int LEFT_HANDLE_GRABBED = 1; 10888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown public static final int RIGHT_HANDLE_GRABBED = 2; 109e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 110e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 111e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Whether the user has triggered something (e.g dragging the left handle all the way over to 112e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the right). 113e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 114e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private boolean mTriggered = false; 115e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 116e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Vibration (haptic feedback) 117e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private Vibrator mVibrator; 1189485aec1df6094b7d2497212c8fe04a6d459b4cdJim Miller private static final long VIBRATE_SHORT = 20; // msec 1199485aec1df6094b7d2497212c8fe04a6d459b4cdJim Miller private static final long VIBRATE_LONG = 20; // msec 120e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 121e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 122e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The drawable for the arrows need to be scrunched this many dips towards the rotary bg below 123e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * it. 124e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 125e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final int ARROW_SCRUNCH_DIP = 6; 126e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 127e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 128e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * How far inset the left and right circles should be 129e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 130e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private static final int EDGE_PADDING_DIP = 9; 131e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 132e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 1335fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen * How far from the edge of the screen the user must drag to trigger the event. 1345fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen */ 135052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen private static final int EDGE_TRIGGER_DIP = 100; 1365fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen 1375fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen /** 138e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Dimensions of arc in background drawable. 139e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 140e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen static final int OUTER_ROTARY_RADIUS_DIP = 390; 141e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen static final int ROTARY_STROKE_WIDTH_DIP = 83; 142052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen static final int SNAP_BACK_ANIMATION_DURATION_MILLIS = 300; 143052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen static final int SPIN_ANIMATION_DURATION_MILLIS = 800; 144e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 1455fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen private int mEdgeTriggerThresh; 146278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mDimpleWidth; 147278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mBackgroundWidth; 148278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private int mBackgroundHeight; 149896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private final int mOuterRadius; 150896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private final int mInnerRadius; 151896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mDimpleSpacing; 152896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 153896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private VelocityTracker mVelocityTracker; 154896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mMinimumVelocity; 155896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mMaximumVelocity; 156896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 157896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen /** 158896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * The number of dimples we are flinging when we do the "spin" animation. Used to know when to 159896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * wrap the icons back around so they "rotate back" onto the screen. 160896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen * @see #updateAnimation() 161896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen */ 162896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private int mDimplesOfFling = 0; 163896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 16474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen /** 16574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * Either {@link #HORIZONTAL} or {@link #VERTICAL}. 16674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen */ 16774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private int mOrientation; 168896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 169e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 1701ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen public RotarySelector(Context context) { 1711ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen this(context, null); 1721ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 1731ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen 174e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 175e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Constructor used when this widget is created from a layout file. 176e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 177e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public RotarySelector(Context context, AttributeSet attrs) { 178e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen super(context, attrs); 17974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 18074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen TypedArray a = 18174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen context.obtainStyledAttributes(attrs, R.styleable.RotarySelector); 18274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mOrientation = a.getInt(R.styleable.RotarySelector_orientation, HORIZONTAL); 18374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen a.recycle(); 184e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 185e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen Resources r = getResources(); 186e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mDensity = r.getDisplayMetrics().density; 187e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (DBG) log("- Density: " + mDensity); 188e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 189e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Assets (all are BitmapDrawables). 19074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackground = getBitmapFor(R.drawable.jog_dial_bg); 191c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mDimple = getBitmapFor(R.drawable.jog_dial_dimple); 192d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller mDimpleDim = getBitmapFor(R.drawable.jog_dial_dimple_dim); 193e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 19474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowLongLeft = getBitmapFor(R.drawable.jog_dial_arrow_long_left_green); 19574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowLongRight = getBitmapFor(R.drawable.jog_dial_arrow_long_right_red); 19674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowShortLeftAndRight = getBitmapFor(R.drawable.jog_dial_arrow_short_left_and_right); 1971ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen 198896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInterpolator = new DecelerateInterpolator(1f); 1995fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen 2005fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen mEdgeTriggerThresh = (int) (mDensity * EDGE_TRIGGER_DIP); 201278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen 202c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mDimpleWidth = mDimple.getWidth(); 203278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen 20474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth = mBackground.getWidth(); 20574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundHeight = mBackground.getHeight(); 206896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius = (int) (mDensity * OUTER_ROTARY_RADIUS_DIP); 207896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius = (int) ((OUTER_ROTARY_RADIUS_DIP - ROTARY_STROKE_WIDTH_DIP) * mDensity); 208896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 209896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final ViewConfiguration configuration = ViewConfiguration.get(mContext); 210896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mMinimumVelocity = configuration.getScaledMinimumFlingVelocity() * 2; 211896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 212896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 213896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 21474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private Bitmap getBitmapFor(int resId) { 21574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen return BitmapFactory.decodeResource(getContext().getResources(), resId); 21674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 21774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 218896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen @Override 219896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen protected void onSizeChanged(int w, int h, int oldw, int oldh) { 220896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen super.onSizeChanged(w, h, oldw, oldh); 221896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 22274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int edgePadding = (int) (EDGE_PADDING_DIP * mDensity); 22374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mLeftHandleX = edgePadding + mDimpleWidth / 2; 22474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int length = isHoriz() ? w : h; 22574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRightHandleX = length - edgePadding - mDimpleWidth / 2; 22674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mDimpleSpacing = (length / 2) - mLeftHandleX; 22774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 22874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // bg matrix only needs to be calculated once 22974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.setTranslate(0, 0); 23074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 23174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // set up matrix for translating drawing of background and arrow assets 23274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int left = w - mBackgroundHeight; 23374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.preRotate(-90, 0, 0); 23474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.postTranslate(left, h); 23574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 23674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 23774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBgMatrix.postTranslate(0, h - mBackgroundHeight); 23874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 23974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 240896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 24174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private boolean isHoriz() { 24274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen return mOrientation == HORIZONTAL; 243e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 244e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 245e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 246e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Sets the left handle icon to a given resource. 247e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 248e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The resource should refer to a Drawable object, or use 0 to remove 249e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the icon. 250e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 251e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param resId the resource ID. 252e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 253e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setLeftHandleResource(int resId) { 254e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (resId != 0) { 255c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mLeftHandleIcon = getBitmapFor(resId); 256e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 257e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen invalidate(); 258e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 259e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 260e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 261e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Sets the right handle icon to a given resource. 262e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 263e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The resource should refer to a Drawable object, or use 0 to remove 264e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * the icon. 265e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 266e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param resId the resource ID. 267e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 268e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setRightHandleResource(int resId) { 269e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (resId != 0) { 270c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen mRightHandleIcon = getBitmapFor(resId); 271e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 272e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen invalidate(); 273e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 274e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 275c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen 276e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 277e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 27874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int length = isHoriz() ? 27974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen MeasureSpec.getSize(widthMeasureSpec) : 28074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen MeasureSpec.getSize(heightMeasureSpec); 28174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int arrowScrunch = (int) (ARROW_SCRUNCH_DIP * mDensity); 28274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int arrowH = mArrowShortLeftAndRight.getHeight(); 283e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 284e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // by making the height less than arrow + bg, arrow and bg will be scrunched together, 285e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // overlaying somewhat (though on transparent portions of the drawable). 286e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // this works because the arrows are drawn from the top, and the rotary bg is drawn 287e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // from the bottom. 28874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int height = mBackgroundHeight + arrowH - arrowScrunch; 289e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 29074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 29174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen setMeasuredDimension(length, height); 29274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 29374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen setMeasuredDimension(height, length); 29474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 29574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 296e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 297e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 298e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen protected void onDraw(Canvas canvas) { 299e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen super.onDraw(canvas); 30074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 30174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int width = getWidth(); 30274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 3035037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (VISUAL_DEBUG) { 3045037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller // draw bounding box around widget 3055037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller mPaint.setColor(0xffff0000); 3065037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller mPaint.setStyle(Paint.Style.STROKE); 3075037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller canvas.drawRect(0, 0, width, getHeight(), mPaint); 3085037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } 309e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 310e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int height = getHeight(); 311e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 312e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // update animating state before we draw anything 313278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen if (mAnimating) { 314896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen updateAnimation(); 315e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 316e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 317e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Background: 31874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mBackground, mBgMatrix, mPaint); 319e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 320e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Draw the correct arrow(s) depending on the current state: 32174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.reset(); 322e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen switch (mGrabbedState) { 323e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case NOTHING_GRABBED: 32474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen //mArrowShortLeftAndRight; 325e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 326e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case LEFT_HANDLE_GRABBED: 32774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.setTranslate(0, 0); 32874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 32974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.preRotate(-90, 0, 0); 33074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.postTranslate(0, height); 33174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 33274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mArrowLongLeft, mArrowMatrix, mPaint); 333e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 334e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen case RIGHT_HANDLE_GRABBED: 33574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.setTranslate(0, 0); 33674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (!isHoriz()) { 33774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.preRotate(-90, 0, 0); 33874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // since bg width is > height of screen in landscape mode... 33974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mArrowMatrix.postTranslate(0, height + (mBackgroundWidth - height)); 34074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 34174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen canvas.drawBitmap(mArrowLongRight, mArrowMatrix, mPaint); 342e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen break; 343e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen default: 344e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen throw new IllegalStateException("invalid mGrabbedState: " + mGrabbedState); 345e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 346e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 34774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int bgHeight = mBackgroundHeight; 34874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int bgTop = isHoriz() ? 34974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen height - bgHeight: 35074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen width - bgHeight; 3515037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller 3525037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (VISUAL_DEBUG) { 3535037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller // draw circle bounding arc drawable: good sanity check we're doing the math correctly 3545037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller float or = OUTER_ROTARY_RADIUS_DIP * mDensity; 3555037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int vOffset = mBackgroundWidth - height; 3565037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int midX = isHoriz() ? width / 2 : mBackgroundWidth / 2 - vOffset; 3575037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (isHoriz()) { 3585037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller canvas.drawCircle(midX, or + bgTop, or, mPaint); 3595037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } else { 3605037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller canvas.drawCircle(or + bgTop, midX, or, mPaint); 3615037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } 3625037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller } 363d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller 36474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // left dimple / icon 365e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen { 366896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int xOffset = mLeftHandleX + mRotaryOffsetX; 367e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int drawableY = getYOnArc( 36874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 369896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 370896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 371e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen xOffset); 3725037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int x = isHoriz() ? xOffset : drawableY + bgTop; 3735037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int y = isHoriz() ? drawableY + bgTop : height - xOffset; 3745037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (mGrabbedState != RIGHT_HANDLE_GRABBED) { 3755037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimple, canvas, x, y); 3765037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mLeftHandleIcon, canvas, x, y); 37774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 3785037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, x, y); 3795fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen } 380e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 381e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 38274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // center dimple 38374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen { 38474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int xOffset = isHoriz() ? 38574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen width / 2 + mRotaryOffsetX: 38674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen height / 2 + mRotaryOffsetX; 387e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int drawableY = getYOnArc( 38874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 389896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 390896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 391e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen xOffset); 392e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 39374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 3945037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, xOffset, drawableY + bgTop); 39574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 39674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // vertical 3975037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - xOffset); 39874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 399e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 400e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 40174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // right dimple / icon 402e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen { 403896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int xOffset = mRightHandleX + mRotaryOffsetX; 404e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int drawableY = getYOnArc( 40574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 406896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 407896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 408e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen xOffset); 409e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 4105037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int x = isHoriz() ? xOffset : drawableY + bgTop; 4115037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller final int y = isHoriz() ? drawableY + bgTop : height - xOffset; 4125037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller if (mGrabbedState != LEFT_HANDLE_GRABBED) { 4135037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimple, canvas, x, y); 4145037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mRightHandleIcon, canvas, x, y); 41574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 4165037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, x, y); 4175fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen } 418e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 419e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 420896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // draw extra left hand dimples 421896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen int dimpleLeft = mRotaryOffsetX + mLeftHandleX - mDimpleSpacing; 422896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int halfdimple = mDimpleWidth / 2; 423896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen while (dimpleLeft > -halfdimple) { 424896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int drawableY = getYOnArc( 42574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 426896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 427896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 428896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleLeft); 429896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 43074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 4315037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, dimpleLeft, drawableY + bgTop); 43274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 4335037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleLeft); 43474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 435896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleLeft -= mDimpleSpacing; 436896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 437896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 438896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // draw extra right hand dimples 439896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen int dimpleRight = mRotaryOffsetX + mRightHandleX + mDimpleSpacing; 440896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int rightThresh = mRight + halfdimple; 441896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen while (dimpleRight < rightThresh) { 442896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int drawableY = getYOnArc( 44374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mBackgroundWidth, 444896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInnerRadius, 445896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mOuterRadius, 446896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleRight); 447896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 44874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (isHoriz()) { 4495037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, dimpleRight, drawableY + bgTop); 45074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else { 4515037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleRight); 45274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } 453896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen dimpleRight += mDimpleSpacing; 454896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 455e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 456e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 457e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 45874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * Assuming bitmap is a bounding box around a piece of an arc drawn by two concentric circles 459e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * (as the background drawable for the rotary widget is), and given an x coordinate along the 460e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * drawable, return the y coordinate of a point on the arc that is between the two concentric 461e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * circles. The resulting y combined with the incoming x is a point along the circle in 462e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * between the two concentric circles. 463e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 46474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen * @param backgroundWidth The width of the asset (the bottom of the box surrounding the arc). 465e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param innerRadius The radius of the circle that intersects the drawable at the bottom two 466e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * corders of the drawable (top two corners in terms of drawing coordinates). 467e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param outerRadius The radius of the circle who's top most point is the top center of the 468e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * drawable (bottom center in terms of drawing coordinates). 46974646ad618a9ca289efa99b4a822e66ca61b8f95Karl 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 470e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * in between the two concentric circles. 471e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 47274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen private int getYOnArc(int backgroundWidth, int innerRadius, int outerRadius, int x) { 473e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 474e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the hypotenuse 475e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int halfWidth = (outerRadius - innerRadius) / 2; 476e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int middleRadius = innerRadius + halfWidth; 477e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 478e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the bottom leg of the triangle 47974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int triangleBottom = (backgroundWidth / 2) - x; 480e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 481e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // "Our offense is like the pythagorean theorem: There is no answer!" - Shaquille O'Neal 482e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen final int triangleY = 483e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen (int) Math.sqrt(middleRadius * middleRadius - triangleBottom * triangleBottom); 484e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 485e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // convert to drawing coordinates: 486e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // middleRadius - triangleY = 487e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // the vertical distance from the outer edge of the circle to the desired point 488e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // from there we add the distance from the top of the drawable to the middle circle 489e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return middleRadius - triangleY + halfWidth; 490e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 491e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 492e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 493e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Handle touch screen events. 494e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 495e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param event The motion event. 496e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @return True if the event was handled, false otherwise. 497e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 498e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen @Override 499e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public boolean onTouchEvent(MotionEvent event) { 500278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen if (mAnimating) { 501e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return true; 502e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 503896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker == null) { 504896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = VelocityTracker.obtain(); 505896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 506896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.addMovement(event); 507896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 50874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int height = getHeight(); 509e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 51074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int eventX = isHoriz() ? 51174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) event.getX(): 51274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen height - ((int) event.getY()); 513278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen final int hitWindow = mDimpleWidth; 514e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 5151ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen final int action = event.getAction(); 5161ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen switch (action) { 5171ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_DOWN: 5181ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-down"); 5191ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = false; 5201ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState != NOTHING_GRABBED) { 5211ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen reset(); 5221ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 523e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 5241ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (eventX < mLeftHandleX + hitWindow) { 525896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mLeftHandleX; 52688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(LEFT_HANDLE_GRABBED); 5271ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5281ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen vibrate(VIBRATE_SHORT); 5291ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (eventX > mRightHandleX - hitWindow) { 530896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mRightHandleX; 53188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(RIGHT_HANDLE_GRABBED); 5321ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5331ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen vibrate(VIBRATE_SHORT); 534e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 5351ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 536e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 5371ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_MOVE: 5381ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-move"); 5391ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState == LEFT_HANDLE_GRABBED) { 540896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mLeftHandleX; 5411ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 54274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rightThresh = isHoriz() ? getRight() : height; 54374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen if (eventX >= rightThresh - mEdgeTriggerThresh && !mTriggered) { 5441ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = true; 545278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen dispatchTriggerEvent(OnDialTriggerListener.LEFT_HANDLE); 546896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final VelocityTracker velocityTracker = mVelocityTracker; 547896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 54874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rawVelocity = isHoriz() ? 54974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) velocityTracker.getXVelocity(): 55074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen -(int) velocityTracker.getYVelocity(); 55174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int velocity = Math.max(mMinimumVelocity, rawVelocity); 552896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = Math.max( 553896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 8, 554896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen Math.abs(velocity / mDimpleSpacing)); 555896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimationWithVelocity( 556896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen eventX - mLeftHandleX, 557896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling * mDimpleSpacing, 558896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocity); 5591ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5601ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (mGrabbedState == RIGHT_HANDLE_GRABBED) { 561896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = eventX - mRightHandleX; 5621ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 5635fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen if (eventX <= mEdgeTriggerThresh && !mTriggered) { 5641ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen mTriggered = true; 565278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen dispatchTriggerEvent(OnDialTriggerListener.RIGHT_HANDLE); 566896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final VelocityTracker velocityTracker = mVelocityTracker; 567896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 56874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int rawVelocity = isHoriz() ? 56974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen (int) velocityTracker.getXVelocity(): 57074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen - (int) velocityTracker.getYVelocity(); 57174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final int velocity = Math.min(-mMinimumVelocity, rawVelocity); 572896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = Math.max( 573896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 8, 574896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen Math.abs(velocity / mDimpleSpacing)); 575896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimationWithVelocity( 576896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen eventX - mRightHandleX, 577896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen -(mDimplesOfFling * mDimpleSpacing), 578896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen velocity); 5791ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5801ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 5811ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 5821ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_UP: 5831ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-up"); 5841ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen // handle animating back to start if they didn't trigger 5851ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (mGrabbedState == LEFT_HANDLE_GRABBED 5861ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen && Math.abs(eventX - mLeftHandleX) > 5) { 587278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen // set up "snap back" animation 588896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimation(eventX - mLeftHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); 5891ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } else if (mGrabbedState == RIGHT_HANDLE_GRABBED 5901ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen && Math.abs(eventX - mRightHandleX) > 5) { 591278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen // set up "snap back" animation 592896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen startAnimation(eventX - mRightHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); 5931ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen } 594896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = 0; 59588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 5961ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 597896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker != null) { 598896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.recycle(); // wishin' we had generational GC 599896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = null; 600896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 6011ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 6021ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen case MotionEvent.ACTION_CANCEL: 6031ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen if (DBG) log("touch-cancel"); 6041ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen reset(); 6051ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen invalidate(); 606896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mVelocityTracker != null) { 607896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker.recycle(); 608896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mVelocityTracker = null; 609896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 6101ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen break; 611e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 612e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen return true; 613e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 614e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 615896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void startAnimation(int startX, int endX, int duration) { 616896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimating = true; 617896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationStartTime = currentAnimationTimeMillis(); 618896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationDuration = duration; 619896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXStart = startX; 620896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXEnd = endX; 62188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 622896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = 0; 623896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 624896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 625896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 626896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void startAnimationWithVelocity(int startX, int endX, int pixelsPerSecond) { 627896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimating = true; 628896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationStartTime = currentAnimationTimeMillis(); 629896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimationDuration = 1000 * (endX - startX) / pixelsPerSecond; 630896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXStart = startX; 631896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mAnimatingDeltaXEnd = endX; 63288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 633896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 634896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 635896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 636896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen private void updateAnimation() { 637896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final long millisSoFar = currentAnimationTimeMillis() - mAnimationStartTime; 638896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final long millisLeft = mAnimationDuration - millisSoFar; 639896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int totalDeltaX = mAnimatingDeltaXStart - mAnimatingDeltaXEnd; 64074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen final boolean goingRight = totalDeltaX < 0; 641896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (DBG) log("millisleft for animating: " + millisLeft); 642896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (millisLeft <= 0) { 643896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen reset(); 644896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen return; 645896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 646896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // from 0 to 1 as animation progresses 647896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen float interpolation = 648896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mInterpolator.getInterpolation((float) millisSoFar / mAnimationDuration); 649896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen final int dx = (int) (totalDeltaX * (1 - interpolation)); 650896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = mAnimatingDeltaXEnd + dx; 65174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen 65274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // once we have gone far enough to animate the current buttons off screen, we start 65374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // wrapping the offset back to the other side so that when the animation is finished, 65474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen // the buttons will come back into their original places. 655896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen if (mDimplesOfFling > 0) { 656ff9c54b7214e99b3182f53f3089da0503f3edddcKarl Rosaen if (!goingRight && mRotaryOffsetX < -3 * mDimpleSpacing) { 657896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // wrap around on fling left 65874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRotaryOffsetX += mDimplesOfFling * mDimpleSpacing; 65974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen } else if (goingRight && mRotaryOffsetX > 3 * mDimpleSpacing) { 660896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen // wrap around on fling right 66174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen mRotaryOffsetX -= mDimplesOfFling * mDimpleSpacing; 662896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 663896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 664896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen invalidate(); 665896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen } 666896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen 667e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private void reset() { 668e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mAnimating = false; 669896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mRotaryOffsetX = 0; 670896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen mDimplesOfFling = 0; 67188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown setGrabbedState(NOTHING_GRABBED); 672e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mTriggered = false; 673e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 674e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 675e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 676e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Triggers haptic feedback. 677e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 678e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private synchronized void vibrate(long duration) { 679723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey final boolean hapticEnabled = Settings.System.getIntForUser( 680723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, 681723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey UserHandle.USER_CURRENT) != 0; 682723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey if (hapticEnabled) { 683723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey if (mVibrator == null) { 684723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey mVibrator = (android.os.Vibrator) getContext() 685723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey .getSystemService(Context.VIBRATOR_SERVICE); 686723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey } 6877b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock mVibrator.vibrate(duration, VIBRATION_ATTRIBUTES); 688e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 689e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 690e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 691e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 692c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen * Draw the bitmap so that it's centered 693c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen * on the point (x,y), then draws it using specified canvas. 694e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * TODO: is there already a utility method somewhere for this? 695e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 696c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen private void drawCentered(Bitmap d, Canvas c, int x, int y) { 697c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen int w = d.getWidth(); 698c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen int h = d.getHeight(); 699c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen 700c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen c.drawBitmap(d, x - (w / 2), y - (h / 2), mPaint); 701e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 702e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 703e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 704e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 705e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Registers a callback to be invoked when the dial 706e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * is "triggered" by rotating it one way or the other. 707e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 708e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param l the OnDialTriggerListener to attach to this view 709e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 710e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public void setOnDialTriggerListener(OnDialTriggerListener l) { 711e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen mOnDialTriggerListener = l; 712e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 713e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 714e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 715e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Dispatches a trigger event to our listener. 716e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 717278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen private void dispatchTriggerEvent(int whichHandle) { 718e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen vibrate(VIBRATE_LONG); 719e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen if (mOnDialTriggerListener != null) { 720278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen mOnDialTriggerListener.onDialTrigger(this, whichHandle); 721e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 722e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 723e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 724e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 72588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * Sets the current grabbed state, and dispatches a grabbed state change 72688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * event to our listener. 72788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown */ 72888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown private void setGrabbedState(int newState) { 72988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown if (newState != mGrabbedState) { 73088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown mGrabbedState = newState; 73188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown if (mOnDialTriggerListener != null) { 73288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown mOnDialTriggerListener.onGrabbedStateChange(this, mGrabbedState); 73388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 73488e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 73588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown } 73688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown 73788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown /** 738e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Interface definition for a callback to be invoked when the dial 739e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * is "triggered" by rotating it one way or the other. 740e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 741e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public interface OnDialTriggerListener { 742e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 743e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The dial was triggered because the user grabbed the left handle, 744e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * and rotated the dial clockwise. 745e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 746e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public static final int LEFT_HANDLE = 1; 747e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 748e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 749e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * The dial was triggered because the user grabbed the right handle, 750e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * and rotated the dial counterclockwise. 751e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 752e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen public static final int RIGHT_HANDLE = 2; 753e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 754e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen /** 755e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Called when the dial is triggered. 756e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * 757e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param v The view that was triggered 758e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * @param whichHandle Which "dial handle" the user grabbed, 759278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen * either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}. 760e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */ 761278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen void onDialTrigger(View v, int whichHandle); 76288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown 76388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown /** 76488e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * Called when the "grabbed state" changes (i.e. when 76588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * the user either grabs or releases one of the handles.) 76688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * 76788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * @param v the view that was triggered 76888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * @param grabbedState the new state: either {@link #NOTHING_GRABBED}, 76988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown * {@link #LEFT_HANDLE_GRABBED}, or {@link #RIGHT_HANDLE_GRABBED}. 77088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown */ 77188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown void onGrabbedStateChange(View v, int grabbedState); 772e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 773e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 774e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 775e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen // Debugging / testing code 776e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen 777e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen private void log(String msg) { 778e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen Log.d(LOG_TAG, msg); 779e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen } 780e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen} 781