The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2008 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package android.widget; |
| 18 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 19 | import android.app.AlertDialog; |
| 20 | import android.app.Dialog; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 21 | import android.content.BroadcastReceiver; |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 22 | import android.content.ContentResolver; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 23 | import android.content.Context; |
| 24 | import android.content.Intent; |
| 25 | import android.content.IntentFilter; |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 26 | import android.content.SharedPreferences; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 27 | import android.graphics.PixelFormat; |
| 28 | import android.graphics.Rect; |
| 29 | import android.os.Handler; |
| 30 | import android.os.Message; |
| 31 | import android.os.SystemClock; |
| 32 | import android.provider.Settings; |
| 33 | import android.util.Log; |
| 34 | import android.view.Gravity; |
| 35 | import android.view.KeyEvent; |
| 36 | import android.view.MotionEvent; |
| 37 | import android.view.View; |
| 38 | import android.view.ViewConfiguration; |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 39 | import android.view.Window; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 40 | import android.view.WindowManager; |
| 41 | import android.view.WindowManager.LayoutParams; |
| 42 | import android.view.animation.Animation; |
| 43 | import android.view.animation.AnimationUtils; |
| 44 | import android.view.animation.DecelerateInterpolator; |
| 45 | |
| 46 | // TODO: make sure no px values exist, only dip (scale if necessary from Viewconfiguration) |
| 47 | |
| 48 | /** |
| 49 | * TODO: Docs |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 50 | * |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 51 | * If you are using this with a custom View, please call |
| 52 | * {@link #setVisible(boolean) setVisible(false)} from the |
| 53 | * {@link View#onDetachedFromWindow}. |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 54 | * |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 55 | * @hide |
| 56 | */ |
| 57 | public class ZoomRingController implements ZoomRing.OnZoomRingCallback, |
| 58 | View.OnTouchListener, View.OnKeyListener { |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 59 | |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 60 | private static final int ZOOM_RING_RADIUS_INSET = 24; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 61 | |
| 62 | private static final int ZOOM_RING_RECENTERING_DURATION = 500; |
| 63 | |
| 64 | private static final String TAG = "ZoomRing"; |
| 65 | |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 66 | public static final boolean USE_OLD_ZOOM = false; |
The Android Open Source Project | 3dec7d5 | 2009-03-02 22:54:33 -0800 | [diff] [blame] | 67 | public static boolean useOldZoom(Context context) { |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 68 | return Settings.System.getInt(context.getContentResolver(), "zoom", 1) == 0; |
The Android Open Source Project | 3dec7d5 | 2009-03-02 22:54:33 -0800 | [diff] [blame] | 69 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 70 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 71 | private static final int ZOOM_CONTROLS_TIMEOUT = |
| 72 | (int) ViewConfiguration.getZoomControlsTimeout(); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 73 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 74 | // TODO: move these to ViewConfiguration or re-use existing ones |
| 75 | // TODO: scale px values based on latest from ViewConfiguration |
| 76 | private static final int SECOND_TAP_TIMEOUT = 500; |
| 77 | private static final int ZOOM_RING_DISMISS_DELAY = SECOND_TAP_TIMEOUT / 2; |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 78 | // TODO: view config? at least scaled |
| 79 | private static final int MAX_PAN_GAP = 20; |
| 80 | private static final int MAX_INITIATE_PAN_GAP = 10; |
| 81 | // TODO view config |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 82 | private static final int INITIATE_PAN_DELAY = 300; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 83 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 84 | private static final String SETTING_NAME_SHOWN_TOAST = "shown_zoom_ring_toast"; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 85 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 86 | private Context mContext; |
| 87 | private WindowManager mWindowManager; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 88 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 89 | /** |
| 90 | * The view that is being zoomed by this zoom ring. |
| 91 | */ |
| 92 | private View mOwnerView; |
| 93 | |
| 94 | /** |
| 95 | * The bounds of the owner view in global coordinates. This is recalculated |
| 96 | * each time the zoom ring is shown. |
| 97 | */ |
| 98 | private Rect mOwnerViewBounds = new Rect(); |
| 99 | |
| 100 | /** |
| 101 | * The container that is added as a window. |
| 102 | */ |
| 103 | private FrameLayout mContainer; |
| 104 | private LayoutParams mContainerLayoutParams; |
| 105 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 106 | /** |
| 107 | * The view (or null) that should receive touch events. This will get set if |
| 108 | * the touch down hits the container. It will be reset on the touch up. |
| 109 | */ |
| 110 | private View mTouchTargetView; |
| 111 | /** |
| 112 | * The {@link #mTouchTargetView}'s location in window, set on touch down. |
| 113 | */ |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 114 | private int[] mTouchTargetLocationInWindow = new int[2]; |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 115 | /** |
| 116 | * If the zoom ring is dismissed but the user is still in a touch |
| 117 | * interaction, we set this to true. This will ignore all touch events until |
| 118 | * up/cancel, and then set the owner's touch listener to null. |
| 119 | */ |
| 120 | private boolean mReleaseTouchListenerOnUp; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 121 | |
| 122 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 123 | /* |
| 124 | * Tap-drag is an interaction where the user first taps and then (quickly) |
| 125 | * does the clockwise or counter-clockwise drag. In reality, this is: (down, |
| 126 | * up, down, move in circles, up). This differs from the usual events of: |
| 127 | * (down, up, down, up, down, move in circles, up). While the only |
| 128 | * difference is the omission of an (up, down), for power-users this is a |
| 129 | * pretty big improvement as it now only requires them to focus on the |
| 130 | * screen once (for the first tap down) instead of twice (for the first tap |
| 131 | * down and then to grab the thumb). |
| 132 | */ |
| 133 | private int mTapDragStartX; |
| 134 | private int mTapDragStartY; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 135 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 136 | private static final int TOUCH_MODE_IDLE = 0; |
| 137 | private static final int TOUCH_MODE_WAITING_FOR_SECOND_TAP = 1; |
| 138 | private static final int TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT = 2; |
| 139 | private static final int TOUCH_MODE_FORWARDING_FOR_TAP_DRAG = 3; |
| 140 | private int mTouchMode; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 141 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 142 | private boolean mIsZoomRingVisible; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 143 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 144 | private ZoomRing mZoomRing; |
| 145 | private int mZoomRingWidth; |
| 146 | private int mZoomRingHeight; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 147 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 148 | /** Invokes panning of owner view if the zoom ring is touching an edge. */ |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 149 | private Panner mPanner; |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 150 | private long mTouchingEdgeStartTime; |
| 151 | private boolean mPanningEnabledForThisInteraction; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 152 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 153 | private ImageView mPanningArrows; |
| 154 | private Animation mPanningArrowsEnterAnimation; |
| 155 | private Animation mPanningArrowsExitAnimation; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 156 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 157 | private Rect mTempRect = new Rect(); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 158 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 159 | private OnZoomListener mCallback; |
| 160 | |
| 161 | private ViewConfiguration mViewConfig; |
| 162 | |
| 163 | /** |
| 164 | * When the zoom ring is centered on screen, this will be the x value used |
| 165 | * for the container's layout params. |
| 166 | */ |
| 167 | private int mCenteredContainerX = Integer.MIN_VALUE; |
| 168 | |
| 169 | /** |
| 170 | * When the zoom ring is centered on screen, this will be the y value used |
| 171 | * for the container's layout params. |
| 172 | */ |
| 173 | private int mCenteredContainerY = Integer.MIN_VALUE; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 174 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 175 | /** |
| 176 | * Scroller used to re-center the zoom ring if the user had dragged it to a |
| 177 | * corner and then double-taps any point on the owner view (the owner view |
| 178 | * will center the double-tapped point, but we should re-center the zoom |
| 179 | * ring). |
| 180 | * <p> |
| 181 | * The (x,y) of the scroller is the (x,y) of the container's layout params. |
| 182 | */ |
| 183 | private Scroller mScroller; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 184 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 185 | /** |
| 186 | * When showing the zoom ring, we add the view as a new window. However, |
| 187 | * there is logic that needs to know the size of the zoom ring which is |
| 188 | * determined after it's laid out. Therefore, we must post this logic onto |
| 189 | * the UI thread so it will be exceuted AFTER the layout. This is the logic. |
| 190 | */ |
| 191 | private Runnable mPostedVisibleInitializer; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 192 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 193 | /** |
| 194 | * Only touch from the main thread. |
| 195 | */ |
| 196 | private static Dialog sTutorialDialog; |
| 197 | private static long sTutorialShowTime; |
| 198 | private static final int TUTORIAL_MIN_DISPLAY_TIME = 2000; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 199 | |
| 200 | private IntentFilter mConfigurationChangedFilter = |
| 201 | new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 202 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 203 | private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() { |
| 204 | @Override |
| 205 | public void onReceive(Context context, Intent intent) { |
| 206 | if (!mIsZoomRingVisible) return; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 207 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 208 | mHandler.removeMessages(MSG_POST_CONFIGURATION_CHANGED); |
| 209 | mHandler.sendEmptyMessage(MSG_POST_CONFIGURATION_CHANGED); |
| 210 | } |
| 211 | }; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 212 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 213 | /** Keeps the scroller going (or starts it). */ |
| 214 | private static final int MSG_SCROLLER_TICK = 1; |
| 215 | /** When configuration changes, this is called after the UI thread is idle. */ |
| 216 | private static final int MSG_POST_CONFIGURATION_CHANGED = 2; |
| 217 | /** Used to delay the zoom ring dismissal. */ |
| 218 | private static final int MSG_DISMISS_ZOOM_RING = 3; |
| 219 | |
| 220 | private Handler mHandler = new Handler() { |
| 221 | @Override |
| 222 | public void handleMessage(Message msg) { |
| 223 | switch (msg.what) { |
| 224 | case MSG_SCROLLER_TICK: |
| 225 | onScrollerTick(); |
| 226 | break; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 227 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 228 | case MSG_POST_CONFIGURATION_CHANGED: |
| 229 | onPostConfigurationChanged(); |
| 230 | break; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 231 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 232 | case MSG_DISMISS_ZOOM_RING: |
| 233 | setVisible(false); |
| 234 | break; |
| 235 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 236 | |
| 237 | } |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 238 | }; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 239 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 240 | public ZoomRingController(Context context, View ownerView) { |
| 241 | mContext = context; |
| 242 | mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 243 | |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 244 | mPanner = new Panner(); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 245 | mOwnerView = ownerView; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 246 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 247 | mZoomRing = new ZoomRing(context); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 248 | mZoomRing.setId(com.android.internal.R.id.zoomControls); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 249 | mZoomRing.setLayoutParams(new FrameLayout.LayoutParams( |
| 250 | FrameLayout.LayoutParams.WRAP_CONTENT, |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 251 | FrameLayout.LayoutParams.WRAP_CONTENT, |
| 252 | Gravity.CENTER)); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 253 | mZoomRing.setCallback(this); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 254 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 255 | createPanningArrows(); |
| 256 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 257 | mContainerLayoutParams = new LayoutParams(); |
| 258 | mContainerLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 259 | mContainerLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCHABLE | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 260 | LayoutParams.FLAG_NOT_FOCUSABLE | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 261 | LayoutParams.FLAG_LAYOUT_NO_LIMITS; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 262 | mContainerLayoutParams.height = LayoutParams.WRAP_CONTENT; |
| 263 | mContainerLayoutParams.width = LayoutParams.WRAP_CONTENT; |
| 264 | mContainerLayoutParams.type = LayoutParams.TYPE_APPLICATION_PANEL; |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 265 | mContainerLayoutParams.format = PixelFormat.TRANSPARENT; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 266 | // TODO: make a new animation for this |
| 267 | mContainerLayoutParams.windowAnimations = com.android.internal.R.style.Animation_Dialog; |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 268 | |
| 269 | mContainer = new FrameLayout(context); |
| 270 | mContainer.setLayoutParams(mContainerLayoutParams); |
| 271 | mContainer.setMeasureAllChildren(true); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 272 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 273 | mContainer.addView(mZoomRing); |
| 274 | mContainer.addView(mPanningArrows); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 275 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 276 | mScroller = new Scroller(context, new DecelerateInterpolator()); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 277 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 278 | mViewConfig = ViewConfiguration.get(context); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 279 | } |
| 280 | |
| 281 | private void createPanningArrows() { |
| 282 | // TODO: style |
| 283 | mPanningArrows = new ImageView(mContext); |
| 284 | mPanningArrows.setImageResource(com.android.internal.R.drawable.zoom_ring_arrows); |
| 285 | mPanningArrows.setLayoutParams(new FrameLayout.LayoutParams( |
| 286 | FrameLayout.LayoutParams.WRAP_CONTENT, |
| 287 | FrameLayout.LayoutParams.WRAP_CONTENT, |
| 288 | Gravity.CENTER)); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 289 | mPanningArrows.setVisibility(View.INVISIBLE); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 290 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 291 | mPanningArrowsEnterAnimation = AnimationUtils.loadAnimation(mContext, |
| 292 | com.android.internal.R.anim.fade_in); |
| 293 | mPanningArrowsExitAnimation = AnimationUtils.loadAnimation(mContext, |
| 294 | com.android.internal.R.anim.fade_out); |
| 295 | } |
| 296 | |
| 297 | /** |
| 298 | * Sets the angle (in radians) a user must travel in order for the client to |
| 299 | * get a callback. Once there is a callback, the accumulator resets. For |
| 300 | * example, if you set this to PI/6, it will give a callback every time the |
| 301 | * user moves PI/6 amount on the ring. |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 302 | * |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 303 | * @param callbackThreshold The angle for the callback threshold, in radians |
| 304 | */ |
| 305 | public void setZoomCallbackThreshold(float callbackThreshold) { |
| 306 | mZoomRing.setCallbackThreshold((int) (callbackThreshold * ZoomRing.RADIAN_INT_MULTIPLIER)); |
| 307 | } |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 308 | |
| 309 | /** |
| 310 | * Sets a drawable for the zoom ring track. |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 311 | * |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 312 | * @param drawable The drawable to use for the track. |
| 313 | * @hide Need a better way of doing this, but this one-off for browser so it |
| 314 | * can have its final look for the usability study |
| 315 | */ |
| 316 | public void setZoomRingTrack(int drawable) { |
| 317 | mZoomRing.setBackgroundResource(drawable); |
| 318 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 319 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 320 | public void setCallback(OnZoomListener callback) { |
| 321 | mCallback = callback; |
| 322 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 323 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 324 | public void setThumbAngle(float angle) { |
| 325 | mZoomRing.setThumbAngle((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER)); |
| 326 | } |
| 327 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 328 | public void setThumbAngleAnimated(float angle) { |
| 329 | mZoomRing.setThumbAngleAnimated((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER), 0); |
| 330 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 331 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 332 | public void setResetThumbAutomatically(boolean resetThumbAutomatically) { |
| 333 | mZoomRing.setResetThumbAutomatically(resetThumbAutomatically); |
| 334 | } |
| 335 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 336 | public void setThumbClockwiseBound(float angle) { |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 337 | mZoomRing.setThumbClockwiseBound(angle >= 0 ? |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 338 | (int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) : |
| 339 | Integer.MIN_VALUE); |
| 340 | } |
| 341 | |
| 342 | public void setThumbCounterclockwiseBound(float angle) { |
| 343 | mZoomRing.setThumbCounterclockwiseBound(angle >= 0 ? |
| 344 | (int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) : |
| 345 | Integer.MIN_VALUE); |
| 346 | } |
| 347 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 348 | public boolean isVisible() { |
| 349 | return mIsZoomRingVisible; |
| 350 | } |
| 351 | |
| 352 | public void setVisible(boolean visible) { |
| 353 | |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 354 | if (useOldZoom(mContext)) return; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 355 | |
| 356 | if (visible) { |
| 357 | dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT); |
| 358 | } else { |
| 359 | mPanner.stop(); |
| 360 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 361 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 362 | if (mIsZoomRingVisible == visible) { |
| 363 | return; |
| 364 | } |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 365 | mIsZoomRingVisible = visible; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 366 | |
| 367 | if (visible) { |
| 368 | if (mContainerLayoutParams.token == null) { |
| 369 | mContainerLayoutParams.token = mOwnerView.getWindowToken(); |
| 370 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 371 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 372 | mWindowManager.addView(mContainer, mContainerLayoutParams); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 373 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 374 | if (mPostedVisibleInitializer == null) { |
| 375 | mPostedVisibleInitializer = new Runnable() { |
| 376 | public void run() { |
| 377 | refreshPositioningVariables(); |
| 378 | resetZoomRing(); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 379 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 380 | // TODO: remove this 'update' and just center zoom ring before the |
| 381 | // 'add', but need to make sure we have the width and height (which |
| 382 | // probably can only be retrieved after it's measured, which happens |
| 383 | // after it's added). |
| 384 | mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 385 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 386 | if (mCallback != null) { |
| 387 | mCallback.onVisibilityChanged(true); |
| 388 | } |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 389 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 390 | }; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 391 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 392 | |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 393 | mPanningArrows.setAnimation(null); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 394 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 395 | mHandler.post(mPostedVisibleInitializer); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 396 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 397 | // Handle configuration changes when visible |
| 398 | mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 399 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 400 | // Steal key/touches events from the owner |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 401 | mOwnerView.setOnKeyListener(this); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 402 | mOwnerView.setOnTouchListener(this); |
| 403 | mReleaseTouchListenerOnUp = false; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 404 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 405 | } else { |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 406 | // Don't want to steal any more keys/touches |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 407 | mOwnerView.setOnKeyListener(null); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 408 | if (mTouchTargetView != null) { |
| 409 | // We are still stealing the touch events for this touch |
| 410 | // sequence, so release the touch listener later |
| 411 | mReleaseTouchListenerOnUp = true; |
| 412 | } else { |
| 413 | mOwnerView.setOnTouchListener(null); |
| 414 | } |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 415 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 416 | // No longer care about configuration changes |
| 417 | mContext.unregisterReceiver(mConfigurationChangedReceiver); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 418 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 419 | mWindowManager.removeView(mContainer); |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 420 | mHandler.removeCallbacks(mPostedVisibleInitializer); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 421 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 422 | if (mCallback != null) { |
| 423 | mCallback.onVisibilityChanged(false); |
| 424 | } |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 425 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 426 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 427 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 428 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 429 | /** |
| 430 | * TODO: docs |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 431 | * |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 432 | * Notes: |
| 433 | * - Touch dispatching is different. Only direct children who are clickable are eligble for touch events. |
| 434 | * - Please ensure you set your View to INVISIBLE not GONE when hiding it. |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 435 | * |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 436 | * @return |
| 437 | */ |
| 438 | public FrameLayout getContainer() { |
| 439 | return mContainer; |
| 440 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 441 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 442 | public int getZoomRingId() { |
| 443 | return mZoomRing.getId(); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 444 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 445 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 446 | private void dismissZoomRingDelayed(int delay) { |
| 447 | mHandler.removeMessages(MSG_DISMISS_ZOOM_RING); |
| 448 | mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_RING, delay); |
| 449 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 450 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 451 | private void resetZoomRing() { |
| 452 | mScroller.abortAnimation(); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 453 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 454 | mContainerLayoutParams.x = mCenteredContainerX; |
| 455 | mContainerLayoutParams.y = mCenteredContainerY; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 456 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 457 | // Reset the thumb |
| 458 | mZoomRing.resetThumbAngle(); |
| 459 | } |
| 460 | |
| 461 | /** |
| 462 | * Should be called by the client for each event belonging to the second tap |
| 463 | * (the down, move, up, and cancel events). |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 464 | * |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 465 | * @param event The event belonging to the second tap. |
| 466 | * @return Whether the event was consumed. |
| 467 | */ |
| 468 | public boolean handleDoubleTapEvent(MotionEvent event) { |
The Android Open Source Project | 3dec7d5 | 2009-03-02 22:54:33 -0800 | [diff] [blame] | 469 | int action = event.getAction(); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 470 | |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 471 | // TODO: make sure this works well with the |
| 472 | // ownerView.setOnTouchListener(this) instead of window receiving |
| 473 | // touches |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 474 | if (action == MotionEvent.ACTION_DOWN) { |
| 475 | mTouchMode = TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT; |
| 476 | int x = (int) event.getX(); |
| 477 | int y = (int) event.getY(); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 478 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 479 | refreshPositioningVariables(); |
| 480 | setVisible(true); |
| 481 | centerPoint(x, y); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 482 | ensureZoomRingIsCentered(); |
| 483 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 484 | // Tap drag mode stuff |
| 485 | mTapDragStartX = x; |
| 486 | mTapDragStartY = y; |
| 487 | |
| 488 | } else if (action == MotionEvent.ACTION_CANCEL) { |
| 489 | mTouchMode = TOUCH_MODE_IDLE; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 490 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 491 | } else { // action is move or up |
| 492 | switch (mTouchMode) { |
| 493 | case TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT: { |
| 494 | switch (action) { |
| 495 | case MotionEvent.ACTION_MOVE: |
| 496 | int x = (int) event.getX(); |
| 497 | int y = (int) event.getY(); |
| 498 | if (Math.abs(x - mTapDragStartX) > mViewConfig.getScaledTouchSlop() || |
| 499 | Math.abs(y - mTapDragStartY) > |
| 500 | mViewConfig.getScaledTouchSlop()) { |
| 501 | mZoomRing.setTapDragMode(true, x, y); |
| 502 | mTouchMode = TOUCH_MODE_FORWARDING_FOR_TAP_DRAG; |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 503 | setTouchTargetView(mZoomRing); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 504 | } |
| 505 | return true; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 506 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 507 | case MotionEvent.ACTION_UP: |
| 508 | mTouchMode = TOUCH_MODE_IDLE; |
| 509 | break; |
| 510 | } |
| 511 | break; |
| 512 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 513 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 514 | case TOUCH_MODE_FORWARDING_FOR_TAP_DRAG: { |
| 515 | switch (action) { |
| 516 | case MotionEvent.ACTION_MOVE: |
| 517 | giveTouchToZoomRing(event); |
| 518 | return true; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 519 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 520 | case MotionEvent.ACTION_UP: |
| 521 | mTouchMode = TOUCH_MODE_IDLE; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 522 | |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 523 | /* |
| 524 | * This is a power-user feature that only shows the |
| 525 | * zoom while the user is performing the tap-drag. |
| 526 | * That means once it is released, the zoom ring |
| 527 | * should disappear. |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 528 | */ |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 529 | mZoomRing.setTapDragMode(false, (int) event.getX(), (int) event.getY()); |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 530 | dismissZoomRingDelayed(0); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 531 | break; |
| 532 | } |
| 533 | break; |
| 534 | } |
| 535 | } |
| 536 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 537 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 538 | return true; |
| 539 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 540 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 541 | private void ensureZoomRingIsCentered() { |
| 542 | LayoutParams lp = mContainerLayoutParams; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 543 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 544 | if (lp.x != mCenteredContainerX || lp.y != mCenteredContainerY) { |
| 545 | int width = mContainer.getWidth(); |
| 546 | int height = mContainer.getHeight(); |
| 547 | mScroller.startScroll(lp.x, lp.y, mCenteredContainerX - lp.x, |
| 548 | mCenteredContainerY - lp.y, ZOOM_RING_RECENTERING_DURATION); |
| 549 | mHandler.sendEmptyMessage(MSG_SCROLLER_TICK); |
| 550 | } |
| 551 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 552 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 553 | private void refreshPositioningVariables() { |
| 554 | mZoomRingWidth = mZoomRing.getWidth(); |
| 555 | mZoomRingHeight = mZoomRing.getHeight(); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 556 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 557 | // Calculate the owner view's bounds |
| 558 | mOwnerView.getGlobalVisibleRect(mOwnerViewBounds); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 559 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 560 | // Get the center |
| 561 | Gravity.apply(Gravity.CENTER, mContainer.getWidth(), mContainer.getHeight(), |
| 562 | mOwnerViewBounds, mTempRect); |
| 563 | mCenteredContainerX = mTempRect.left; |
| 564 | mCenteredContainerY = mTempRect.top; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 565 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 566 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 567 | /** |
| 568 | * Centers the point (in owner view's coordinates). |
| 569 | */ |
| 570 | private void centerPoint(int x, int y) { |
| 571 | if (mCallback != null) { |
| 572 | mCallback.onCenter(x, y); |
| 573 | } |
| 574 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 575 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 576 | private void giveTouchToZoomRing(MotionEvent event) { |
| 577 | int rawX = (int) event.getRawX(); |
| 578 | int rawY = (int) event.getRawY(); |
| 579 | int x = rawX - mContainerLayoutParams.x - mZoomRing.getLeft(); |
| 580 | int y = rawY - mContainerLayoutParams.y - mZoomRing.getTop(); |
| 581 | mZoomRing.handleTouch(event.getAction(), event.getEventTime(), x, y, rawX, rawY); |
| 582 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 583 | |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 584 | public void onZoomRingSetMovableHintVisible(boolean visible) { |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 585 | setPanningArrowsVisible(visible); |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 586 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 587 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 588 | public void onUserInteractionStarted() { |
| 589 | mHandler.removeMessages(MSG_DISMISS_ZOOM_RING); |
| 590 | } |
| 591 | |
| 592 | public void onUserInteractionStopped() { |
| 593 | dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT); |
| 594 | } |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 595 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 596 | public void onZoomRingMovingStarted() { |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 597 | mScroller.abortAnimation(); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 598 | mTouchingEdgeStartTime = 0; |
| 599 | if (mCallback != null) { |
| 600 | mCallback.onBeginPan(); |
| 601 | } |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 602 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 603 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 604 | private void setPanningArrowsVisible(boolean visible) { |
| 605 | mPanningArrows.startAnimation(visible ? mPanningArrowsEnterAnimation |
| 606 | : mPanningArrowsExitAnimation); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 607 | mPanningArrows.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 608 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 609 | |
| 610 | public boolean onZoomRingMoved(int deltaX, int deltaY) { |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 611 | WindowManager.LayoutParams lp = mContainerLayoutParams; |
| 612 | Rect ownerBounds = mOwnerViewBounds; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 613 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 614 | int zoomRingLeft = mZoomRing.getLeft(); |
| 615 | int zoomRingTop = mZoomRing.getTop(); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 616 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 617 | int newX = lp.x + deltaX; |
| 618 | int newZoomRingX = newX + zoomRingLeft; |
| 619 | newZoomRingX = (newZoomRingX <= ownerBounds.left) ? ownerBounds.left : |
| 620 | (newZoomRingX + mZoomRingWidth > ownerBounds.right) ? |
| 621 | ownerBounds.right - mZoomRingWidth : newZoomRingX; |
| 622 | lp.x = newZoomRingX - zoomRingLeft; |
| 623 | |
| 624 | int newY = lp.y + deltaY; |
| 625 | int newZoomRingY = newY + zoomRingTop; |
| 626 | newZoomRingY = (newZoomRingY <= ownerBounds.top) ? ownerBounds.top : |
| 627 | (newZoomRingY + mZoomRingHeight > ownerBounds.bottom) ? |
| 628 | ownerBounds.bottom - mZoomRingHeight : newZoomRingY; |
| 629 | lp.y = newZoomRingY - zoomRingTop; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 630 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 631 | mWindowManager.updateViewLayout(mContainer, lp); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 632 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 633 | // Check for pan |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 634 | boolean horizontalPanning = true; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 635 | int leftGap = newZoomRingX - ownerBounds.left; |
| 636 | if (leftGap < MAX_PAN_GAP) { |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 637 | if (shouldPan(leftGap)) { |
| 638 | mPanner.setHorizontalStrength(-getStrengthFromGap(leftGap)); |
| 639 | } |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 640 | } else { |
| 641 | int rightGap = ownerBounds.right - (lp.x + mZoomRingWidth + zoomRingLeft); |
| 642 | if (rightGap < MAX_PAN_GAP) { |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 643 | if (shouldPan(rightGap)) { |
| 644 | mPanner.setHorizontalStrength(getStrengthFromGap(rightGap)); |
| 645 | } |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 646 | } else { |
| 647 | mPanner.setHorizontalStrength(0); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 648 | horizontalPanning = false; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 649 | } |
| 650 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 651 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 652 | int topGap = newZoomRingY - ownerBounds.top; |
| 653 | if (topGap < MAX_PAN_GAP) { |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 654 | if (shouldPan(topGap)) { |
| 655 | mPanner.setVerticalStrength(-getStrengthFromGap(topGap)); |
| 656 | } |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 657 | } else { |
| 658 | int bottomGap = ownerBounds.bottom - (lp.y + mZoomRingHeight + zoomRingTop); |
| 659 | if (bottomGap < MAX_PAN_GAP) { |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 660 | if (shouldPan(bottomGap)) { |
| 661 | mPanner.setVerticalStrength(getStrengthFromGap(bottomGap)); |
| 662 | } |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 663 | } else { |
| 664 | mPanner.setVerticalStrength(0); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 665 | if (!horizontalPanning) { |
| 666 | // Neither are panning, reset any timer to start pan mode |
| 667 | mTouchingEdgeStartTime = 0; |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 668 | mPanningEnabledForThisInteraction = false; |
| 669 | mPanner.stop(); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 670 | } |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 671 | } |
| 672 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 673 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 674 | return true; |
| 675 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 676 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 677 | private boolean shouldPan(int gap) { |
| 678 | if (mPanningEnabledForThisInteraction) return true; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 679 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 680 | if (gap < MAX_INITIATE_PAN_GAP) { |
| 681 | long time = SystemClock.elapsedRealtime(); |
| 682 | if (mTouchingEdgeStartTime != 0 && |
| 683 | mTouchingEdgeStartTime + INITIATE_PAN_DELAY < time) { |
| 684 | mPanningEnabledForThisInteraction = true; |
| 685 | return true; |
| 686 | } else if (mTouchingEdgeStartTime == 0) { |
| 687 | mTouchingEdgeStartTime = time; |
| 688 | } else { |
| 689 | } |
| 690 | } else { |
| 691 | // Moved away from the initiate pan gap, so reset the timer |
| 692 | mTouchingEdgeStartTime = 0; |
| 693 | } |
| 694 | return false; |
| 695 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 696 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 697 | public void onZoomRingMovingStopped() { |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 698 | mPanner.stop(); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 699 | setPanningArrowsVisible(false); |
| 700 | if (mCallback != null) { |
| 701 | mCallback.onEndPan(); |
| 702 | } |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 703 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 704 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 705 | private int getStrengthFromGap(int gap) { |
| 706 | return gap > MAX_PAN_GAP ? 0 : |
| 707 | (MAX_PAN_GAP - gap) * 100 / MAX_PAN_GAP; |
| 708 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 709 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 710 | public void onZoomRingThumbDraggingStarted() { |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 711 | if (mCallback != null) { |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 712 | mCallback.onBeginDrag(); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 713 | } |
| 714 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 715 | |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 716 | public boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle) { |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 717 | if (mCallback != null) { |
| 718 | int deltaZoomLevel = -numLevels; |
| 719 | int globalZoomCenterX = mContainerLayoutParams.x + mZoomRing.getLeft() + |
| 720 | mZoomRingWidth / 2; |
| 721 | int globalZoomCenterY = mContainerLayoutParams.y + mZoomRing.getTop() + |
| 722 | mZoomRingHeight / 2; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 723 | |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 724 | return mCallback.onDragZoom(deltaZoomLevel, |
| 725 | globalZoomCenterX - mOwnerViewBounds.left, |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 726 | globalZoomCenterY - mOwnerViewBounds.top, |
| 727 | (float) startAngle / ZoomRing.RADIAN_INT_MULTIPLIER, |
| 728 | (float) curAngle / ZoomRing.RADIAN_INT_MULTIPLIER); |
| 729 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 730 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 731 | return false; |
| 732 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 733 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 734 | public void onZoomRingThumbDraggingStopped() { |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 735 | if (mCallback != null) { |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 736 | mCallback.onEndDrag(); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 737 | } |
| 738 | } |
| 739 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 740 | public void onZoomRingDismissed(boolean dismissImmediately) { |
| 741 | if (dismissImmediately) { |
| 742 | mHandler.removeMessages(MSG_DISMISS_ZOOM_RING); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 743 | setVisible(false); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 744 | } else { |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 745 | dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 746 | } |
| 747 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 748 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 749 | public void onRingDown(int tickAngle, int touchAngle) { |
| 750 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 751 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 752 | public boolean onTouch(View v, MotionEvent event) { |
| 753 | if (sTutorialDialog != null && sTutorialDialog.isShowing() && |
| 754 | SystemClock.elapsedRealtime() - sTutorialShowTime >= TUTORIAL_MIN_DISPLAY_TIME) { |
| 755 | finishZoomTutorial(); |
| 756 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 757 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 758 | int action = event.getAction(); |
| 759 | |
| 760 | if (mReleaseTouchListenerOnUp) { |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 761 | // The ring was dismissed but we need to throw away all events until the up |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 762 | if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { |
| 763 | mOwnerView.setOnTouchListener(null); |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 764 | setTouchTargetView(null); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 765 | mReleaseTouchListenerOnUp = false; |
| 766 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 767 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 768 | // Eat this event |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 769 | return true; |
| 770 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 771 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 772 | View targetView = mTouchTargetView; |
| 773 | |
| 774 | switch (action) { |
| 775 | case MotionEvent.ACTION_DOWN: |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 776 | targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY()); |
| 777 | setTouchTargetView(targetView); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 778 | break; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 779 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 780 | case MotionEvent.ACTION_UP: |
| 781 | case MotionEvent.ACTION_CANCEL: |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 782 | setTouchTargetView(null); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 783 | break; |
| 784 | } |
| 785 | |
| 786 | if (targetView != null) { |
| 787 | // The upperleft corner of the target view in raw coordinates |
| 788 | int targetViewRawX = mContainerLayoutParams.x + mTouchTargetLocationInWindow[0]; |
| 789 | int targetViewRawY = mContainerLayoutParams.y + mTouchTargetLocationInWindow[1]; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 790 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 791 | MotionEvent containerEvent = MotionEvent.obtain(event); |
| 792 | // Convert the motion event into the target view's coordinates (from |
| 793 | // owner view's coordinates) |
| 794 | containerEvent.offsetLocation(mOwnerViewBounds.left - targetViewRawX, |
| 795 | mOwnerViewBounds.top - targetViewRawY); |
| 796 | boolean retValue = targetView.dispatchTouchEvent(containerEvent); |
| 797 | containerEvent.recycle(); |
| 798 | return retValue; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 799 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 800 | } else { |
| 801 | if (action == MotionEvent.ACTION_DOWN) { |
| 802 | dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY); |
| 803 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 804 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 805 | return false; |
| 806 | } |
| 807 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 808 | |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 809 | private void setTouchTargetView(View view) { |
| 810 | mTouchTargetView = view; |
| 811 | if (view != null) { |
| 812 | view.getLocationInWindow(mTouchTargetLocationInWindow); |
| 813 | } |
| 814 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 815 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 816 | /** |
| 817 | * Returns the View that should receive a touch at the given coordinates. |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 818 | * |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 819 | * @param rawX The raw X. |
| 820 | * @param rawY The raw Y. |
| 821 | * @return The view that should receive the touches, or null if there is not one. |
| 822 | */ |
| 823 | private View getViewForTouch(int rawX, int rawY) { |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 824 | // Check to see if it is touching the ring |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 825 | int containerCenterX = mContainerLayoutParams.x + mContainer.getWidth() / 2; |
| 826 | int containerCenterY = mContainerLayoutParams.y + mContainer.getHeight() / 2; |
| 827 | int distanceFromCenterX = rawX - containerCenterX; |
| 828 | int distanceFromCenterY = rawY - containerCenterY; |
| 829 | int zoomRingRadius = mZoomRingWidth / 2 - ZOOM_RING_RADIUS_INSET; |
| 830 | if (distanceFromCenterX * distanceFromCenterX + |
| 831 | distanceFromCenterY * distanceFromCenterY <= |
| 832 | zoomRingRadius * zoomRingRadius) { |
| 833 | return mZoomRing; |
| 834 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 835 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 836 | // Check to see if it is touching any other clickable View. |
| 837 | // Reverse order so the child drawn on top gets first dibs. |
| 838 | int containerCoordsX = rawX - mContainerLayoutParams.x; |
| 839 | int containerCoordsY = rawY - mContainerLayoutParams.y; |
| 840 | Rect frame = mTempRect; |
| 841 | for (int i = mContainer.getChildCount() - 1; i >= 0; i--) { |
| 842 | View child = mContainer.getChildAt(i); |
| 843 | if (child == mZoomRing || child.getVisibility() != View.VISIBLE || |
| 844 | !child.isClickable()) { |
| 845 | continue; |
| 846 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 847 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 848 | child.getHitRect(frame); |
| 849 | if (frame.contains(containerCoordsX, containerCoordsY)) { |
| 850 | return child; |
| 851 | } |
| 852 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 853 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 854 | return null; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 855 | } |
| 856 | |
| 857 | /** Steals key events from the owner view. */ |
| 858 | public boolean onKey(View v, int keyCode, KeyEvent event) { |
| 859 | switch (keyCode) { |
| 860 | case KeyEvent.KEYCODE_DPAD_LEFT: |
| 861 | case KeyEvent.KEYCODE_DPAD_RIGHT: |
| 862 | // Eat these |
| 863 | return true; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 864 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 865 | case KeyEvent.KEYCODE_DPAD_UP: |
| 866 | case KeyEvent.KEYCODE_DPAD_DOWN: |
| 867 | // Keep the zoom alive a little longer |
| 868 | dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 869 | // They started zooming, hide the thumb arrows |
| 870 | mZoomRing.setThumbArrowsVisible(false); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 871 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 872 | if (mCallback != null && event.getAction() == KeyEvent.ACTION_DOWN) { |
| 873 | mCallback.onSimpleZoom(keyCode == KeyEvent.KEYCODE_DPAD_UP); |
| 874 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 875 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 876 | return true; |
| 877 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 878 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 879 | return false; |
| 880 | } |
| 881 | |
| 882 | private void onScrollerTick() { |
| 883 | if (!mScroller.computeScrollOffset() || !mIsZoomRingVisible) return; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 884 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 885 | mContainerLayoutParams.x = mScroller.getCurrX(); |
| 886 | mContainerLayoutParams.y = mScroller.getCurrY(); |
| 887 | mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams); |
| 888 | |
| 889 | mHandler.sendEmptyMessage(MSG_SCROLLER_TICK); |
| 890 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 891 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 892 | private void onPostConfigurationChanged() { |
| 893 | dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT); |
| 894 | refreshPositioningVariables(); |
| 895 | ensureZoomRingIsCentered(); |
| 896 | } |
| 897 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 898 | /* |
| 899 | * This is static so Activities can call this instead of the Views |
| 900 | * (Activities usually do not have a reference to the ZoomRingController |
| 901 | * instance.) |
| 902 | */ |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 903 | /** |
| 904 | * Shows a "tutorial" (some text) to the user teaching her the new zoom |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 905 | * invocation method. Must call from the main thread. |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 906 | * <p> |
| 907 | * It checks the global system setting to ensure this has not been seen |
| 908 | * before. Furthermore, if the application does not have privilege to write |
| 909 | * to the system settings, it will store this bit locally in a shared |
| 910 | * preference. |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 911 | * |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 912 | * @hide This should only be used by our main apps--browser, maps, and |
| 913 | * gallery |
| 914 | */ |
| 915 | public static void showZoomTutorialOnce(Context context) { |
| 916 | ContentResolver cr = context.getContentResolver(); |
| 917 | if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TOAST, 0) == 1) { |
| 918 | return; |
| 919 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 920 | |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 921 | SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE); |
| 922 | if (sp.getInt(SETTING_NAME_SHOWN_TOAST, 0) == 1) { |
| 923 | return; |
| 924 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 925 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 926 | if (sTutorialDialog != null && sTutorialDialog.isShowing()) { |
| 927 | sTutorialDialog.dismiss(); |
| 928 | } |
| 929 | |
| 930 | sTutorialDialog = new AlertDialog.Builder(context) |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 931 | .setMessage( |
| 932 | com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short) |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 933 | .setIcon(0) |
| 934 | .create(); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 935 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 936 | Window window = sTutorialDialog.getWindow(); |
| 937 | window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 938 | window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 939 | WindowManager.LayoutParams.FLAG_BLUR_BEHIND); |
| 940 | window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | |
| 941 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 942 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 943 | sTutorialDialog.show(); |
| 944 | sTutorialShowTime = SystemClock.elapsedRealtime(); |
| 945 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 946 | |
| 947 | public void finishZoomTutorial() { |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 948 | if (sTutorialDialog == null) return; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 949 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 950 | sTutorialDialog.dismiss(); |
| 951 | sTutorialDialog = null; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 952 | |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 953 | // Record that they have seen the tutorial |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 954 | try { |
| 955 | Settings.System.putInt(mContext.getContentResolver(), SETTING_NAME_SHOWN_TOAST, 1); |
| 956 | } catch (SecurityException e) { |
| 957 | /* |
| 958 | * The app does not have permission to clear this global flag, make |
| 959 | * sure the user does not see the message when he comes back to this |
| 960 | * same app at least. |
| 961 | */ |
| 962 | SharedPreferences sp = mContext.getSharedPreferences("_zoom", Context.MODE_PRIVATE); |
| 963 | sp.edit().putInt(SETTING_NAME_SHOWN_TOAST, 1).commit(); |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 964 | } |
The Android Open Source Project | da996f3 | 2009-02-13 12:57:50 -0800 | [diff] [blame] | 965 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 966 | |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 967 | public void setPannerStartVelocity(float startVelocity) { |
| 968 | mPanner.mStartVelocity = startVelocity; |
| 969 | } |
| 970 | |
| 971 | public void setPannerAcceleration(float acceleration) { |
| 972 | mPanner.mAcceleration = acceleration; |
| 973 | } |
| 974 | |
| 975 | public void setPannerMaxVelocity(float maxVelocity) { |
| 976 | mPanner.mMaxVelocity = maxVelocity; |
| 977 | } |
| 978 | |
| 979 | public void setPannerStartAcceleratingDuration(int duration) { |
| 980 | mPanner.mStartAcceleratingDuration = duration; |
| 981 | } |
| 982 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 983 | private class Panner implements Runnable { |
| 984 | private static final int RUN_DELAY = 15; |
| 985 | private static final float STOP_SLOWDOWN = 0.8f; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 986 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 987 | private final Handler mUiHandler = new Handler(); |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 988 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 989 | private int mVerticalStrength; |
| 990 | private int mHorizontalStrength; |
| 991 | |
| 992 | private boolean mStopping; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 993 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 994 | /** The time this current pan started. */ |
| 995 | private long mStartTime; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 996 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 997 | /** The time of the last callback to pan the map/browser/etc. */ |
| 998 | private long mPreviousCallbackTime; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 999 | |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 1000 | // TODO Adjust to be DPI safe |
| 1001 | private float mStartVelocity = 135; |
| 1002 | private float mAcceleration = 160; |
| 1003 | private float mMaxVelocity = 1000; |
| 1004 | private int mStartAcceleratingDuration = 700; |
| 1005 | private float mVelocity; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1006 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1007 | /** -100 (full left) to 0 (none) to 100 (full right) */ |
| 1008 | public void setHorizontalStrength(int horizontalStrength) { |
| 1009 | if (mHorizontalStrength == 0 && mVerticalStrength == 0 && horizontalStrength != 0) { |
| 1010 | start(); |
| 1011 | } else if (mVerticalStrength == 0 && horizontalStrength == 0) { |
| 1012 | stop(); |
| 1013 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1014 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1015 | mHorizontalStrength = horizontalStrength; |
| 1016 | mStopping = false; |
| 1017 | } |
| 1018 | |
| 1019 | /** -100 (full up) to 0 (none) to 100 (full down) */ |
| 1020 | public void setVerticalStrength(int verticalStrength) { |
| 1021 | if (mHorizontalStrength == 0 && mVerticalStrength == 0 && verticalStrength != 0) { |
| 1022 | start(); |
| 1023 | } else if (mHorizontalStrength == 0 && verticalStrength == 0) { |
| 1024 | stop(); |
| 1025 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1026 | |
| 1027 | mVerticalStrength = verticalStrength; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1028 | mStopping = false; |
| 1029 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1030 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1031 | private void start() { |
| 1032 | mUiHandler.post(this); |
| 1033 | mPreviousCallbackTime = 0; |
| 1034 | mStartTime = 0; |
| 1035 | } |
| 1036 | |
| 1037 | public void stop() { |
| 1038 | mStopping = true; |
| 1039 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1040 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1041 | public void run() { |
| 1042 | if (mStopping) { |
| 1043 | mHorizontalStrength *= STOP_SLOWDOWN; |
| 1044 | mVerticalStrength *= STOP_SLOWDOWN; |
| 1045 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1046 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1047 | if (mHorizontalStrength == 0 && mVerticalStrength == 0) { |
| 1048 | return; |
| 1049 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1050 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1051 | boolean firstRun = mPreviousCallbackTime == 0; |
| 1052 | long curTime = SystemClock.elapsedRealtime(); |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 1053 | int panAmount = getPanAmount(mPreviousCallbackTime, curTime); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1054 | mPreviousCallbackTime = curTime; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1055 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1056 | if (firstRun) { |
| 1057 | mStartTime = curTime; |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 1058 | mVelocity = mStartVelocity; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1059 | } else { |
| 1060 | int panX = panAmount * mHorizontalStrength / 100; |
| 1061 | int panY = panAmount * mVerticalStrength / 100; |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1062 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1063 | if (mCallback != null) { |
| 1064 | mCallback.onPan(panX, panY); |
| 1065 | } |
| 1066 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1067 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1068 | mUiHandler.postDelayed(this, RUN_DELAY); |
| 1069 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1070 | |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 1071 | private int getPanAmount(long previousTime, long currentTime) { |
| 1072 | if (mVelocity > mMaxVelocity) { |
| 1073 | mVelocity = mMaxVelocity; |
| 1074 | } else if (mVelocity < mMaxVelocity) { |
| 1075 | // See if it's time to add in some acceleration |
| 1076 | if (currentTime - mStartTime > mStartAcceleratingDuration) { |
| 1077 | mVelocity += (currentTime - previousTime) * mAcceleration / 1000; |
| 1078 | } |
| 1079 | } |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1080 | |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 1081 | return (int) ((currentTime - previousTime) * mVelocity) / 1000; |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1082 | } |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 1083 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1084 | } |
The Android Open Source Project | 15ab3ea | 2009-02-20 07:38:31 -0800 | [diff] [blame] | 1085 | |
The Android Open Source Project | 076357b | 2009-03-03 14:04:24 -0800 | [diff] [blame^] | 1086 | |
| 1087 | |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1088 | public interface OnZoomListener { |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 1089 | void onBeginDrag(); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1090 | boolean onDragZoom(int deltaZoomLevel, int centerX, int centerY, float startAngle, |
| 1091 | float curAngle); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 1092 | void onEndDrag(); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1093 | void onSimpleZoom(boolean deltaZoomLevel); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 1094 | void onBeginPan(); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1095 | boolean onPan(int deltaX, int deltaY); |
The Android Open Source Project | 3001a03 | 2009-02-19 10:57:31 -0800 | [diff] [blame] | 1096 | void onEndPan(); |
The Android Open Source Project | d24b818 | 2009-02-10 15:44:00 -0800 | [diff] [blame] | 1097 | void onCenter(int x, int y); |
| 1098 | void onVisibilityChanged(boolean visible); |
| 1099 | } |
| 1100 | } |