blob: 41a02f0ed8164c010cbef1cfab6ff8762f1e3ad1 [file] [log] [blame]
Joe Onorato85a02a82009-09-08 12:34:22 -07001/*
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
17package com.android.launcher2;
18
19import android.content.Context;
20import android.os.Handler;
21import android.os.Message;
22import android.os.SystemClock;
23import android.util.Log;
24import android.util.DisplayMetrics;
25import android.view.MotionEvent;
26import android.view.VelocityTracker;
27import android.view.ViewConfiguration;
28
29import java.util.ArrayList;
30
31public class SwipeController {
32 private static final String TAG = "Launcher.SwipeController";
33
34 private static final int FRAME_DELAY = 1000 / 30;
35 private static final float DECAY_CONSTANT = 0.65f;
36 private static final float SPRING_CONSTANT = 0.0009f;
37
38 // configuration
39 private SwipeListener mListener;
40 private int mSlop;
41 private float mSwipeDistance;
42
43 // state
44 private VelocityTracker mVelocityTracker;
45 private boolean mCanceled;
46 private boolean mTracking;
47 private int mDownX;
48 private int mDownY;
49
50 private float mMinDest;
51 private float mMaxDest;
52 private long mFlingTime;
53 private long mLastTime;
54 private int mDirection;
55 private float mVelocity;
56 private float mDest;
57 private float mAmount;
58
59 public interface SwipeListener {
60 public void onStartSwipe();
61 public void onFinishSwipe(int amount);
62 public void onSwipe(float amount);
63 }
64
65 public SwipeController(Context context, SwipeListener listener) {
66 ViewConfiguration config = ViewConfiguration.get(context);
67 mSlop = config.getScaledTouchSlop();
68
69 DisplayMetrics display = context.getResources().getDisplayMetrics();
70 mSwipeDistance = display.heightPixels / 2; // one half of the screen
71
72 mListener = listener;
73 }
74
75 public void setRange(float min, float max) {
76 mMinDest = min;
77 mMaxDest = max;
78 }
79
80 public void cancelSwipe() {
81 mCanceled = true;
82 }
83
84 public boolean onInterceptTouchEvent(MotionEvent ev) {
85 onTouchEvent(ev);
86
87 // After we return true, onIntercept doesn't get called any more, so this is
88 // a good place to do the callback.
89 if (mTracking) {
90 mListener.onStartSwipe();
91 }
92
93 return mTracking;
94 }
95
96 public boolean onTouchEvent(MotionEvent ev) {
97 if (mVelocityTracker == null) {
98 mVelocityTracker = VelocityTracker.obtain();
99 }
100 mVelocityTracker.addMovement(ev);
101
102 final int screenX = (int)ev.getRawX();
103 final int screenY = (int)ev.getRawY();
104
105 final int deltaX = screenX - mDownX;
106 final int deltaY = screenY - mDownY;
107
108 final int action = ev.getAction();
109 switch (action) {
110 case MotionEvent.ACTION_DOWN:
111 // Remember location of down touch
112 mCanceled = false;
113 mTracking = false;
114 mDownX = screenX;
115 mDownY = screenY;
116 break;
117
118 case MotionEvent.ACTION_MOVE:
119 if (!mCanceled && !mTracking) {
120 if (Math.abs(deltaX) > mSlop) {
121 mCanceled = true;
122 mTracking = false;
123 }
124 if (Math.abs(deltaY) > mSlop) {
125 mTracking = true;
126 }
127 }
128 if (mTracking && !mCanceled) {
129 track(screenY);
130 }
131 break;
132
133 case MotionEvent.ACTION_CANCEL:
134 case MotionEvent.ACTION_UP:
135 if (mTracking && !mCanceled) {
136 fling(screenY);
137 }
138 mVelocityTracker.recycle();
139 mVelocityTracker = null;
140 break;
141 }
142
143 return mTracking || mCanceled;
144 }
145
Joe Onoratofb0ca672009-09-14 17:55:46 -0400146 /**
147 * Set the value, performing an animation. Make sure that you have set the
148 * range properly first, otherwise the value may be clamped.
149 */
150 public void animate(float dest) {
151 go(dest, 0);
152 }
153
154 /**
155 * Set the value, but don't perform an animation. Make sure that you have set the
156 * range properly first, otherwise the value may be clamped.
157 */
158 public void setImmediate(float dest) {
159 go(dest, dest);
160 }
161
162 /**
163 * Externally start a swipe. If dest == amount, this will end up just immediately
164 * setting the value, but it will perform the proper start and finish callbacks.
165 */
166 private void go(float dest, float amount) {
167 mListener.onStartSwipe();
168
169 dest = clamp(dest);
170 mDirection = dest > amount ? 1 : -1; // if they're equal it doesn't matter
171 mVelocity = mDirection * 0.002f; // TODO: density.
172 mAmount = amount;
173 mDest = dest;
174
175 mFlingTime = SystemClock.uptimeMillis();
176 mLastTime = 0;
177
178 scheduleAnim();
179 }
180
Joe Onorato85a02a82009-09-08 12:34:22 -0700181 private float clamp(float v) {
182 if (v < mMinDest) {
183 return mMinDest;
184 } else if (v > mMaxDest) {
185 return mMaxDest;
186 } else {
187 return v;
188 }
189 }
190
191 /**
192 * Perform the callbacks.
193 */
194 private void track(int screenY) {
195 mAmount = clamp((screenY - mDownY) / mSwipeDistance);
196 mListener.onSwipe(mAmount);
197 }
198
199 private void fling(int screenY) {
200 mVelocityTracker.computeCurrentVelocity(1);
201
202 mVelocity = mVelocityTracker.getYVelocity() / mSwipeDistance;
Joe Onoratofb0ca672009-09-14 17:55:46 -0400203 Log.d(TAG, "mVelocity=" + mVelocity);
Joe Onorato85a02a82009-09-08 12:34:22 -0700204 mDirection = mVelocity >= 0.0f ? 1 : -1;
205 mAmount = clamp((screenY-mDownY)/mSwipeDistance);
206 if (mAmount < 0) {
207 mDest = clamp(mVelocity < 0 ? -1.0f : 0.0f);
208 } else {
209 mDest = clamp(mVelocity < 0 ? 0.0f : 1.0f);
210 }
211
212 mFlingTime = SystemClock.uptimeMillis();
213 mLastTime = 0;
214
215 scheduleAnim();
216 }
217
218 private void scheduleAnim() {
219 boolean send = true;
220 if (mDirection > 0) {
221 if (mAmount > (mDest - 0.01f)) {
222 send = false;
223 }
224 } else {
225 if (mAmount < (mDest + 0.01f)) {
226 send = false;
227 }
228 }
229 if (send) {
230 mHandler.sendEmptyMessageDelayed(1, FRAME_DELAY);
231 } else {
232 mListener.onFinishSwipe((int)(mAmount >= 0 ? (mAmount+0.5f) : (mAmount-0.5f)));
233 }
234 }
235
236 Handler mHandler = new Handler() {
237 public void handleMessage(Message msg) {
238 long now = SystemClock.uptimeMillis();
239
240 final long t = now - mFlingTime;
241 final long dt = t - mLastTime;
242 mLastTime = t;
243 final float timeSlices = dt / (float)FRAME_DELAY;
244
245 float distance = mDest - mAmount;
246
247 mVelocity += timeSlices * mDirection * SPRING_CONSTANT * distance * distance / 2;
248 mVelocity *= (timeSlices * DECAY_CONSTANT);
249
250 mAmount += timeSlices * mVelocity;
251 mAmount += distance * timeSlices * 0.2f; // cheat
252
253 mListener.onSwipe(mAmount);
254 scheduleAnim();
255 }
256 };
257}
258