blob: 7443b3bbc2bf4b96dd8599ae4ac9886f16e05f3c [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
146 private float clamp(float v) {
147 if (v < mMinDest) {
148 return mMinDest;
149 } else if (v > mMaxDest) {
150 return mMaxDest;
151 } else {
152 return v;
153 }
154 }
155
156 /**
157 * Perform the callbacks.
158 */
159 private void track(int screenY) {
160 mAmount = clamp((screenY - mDownY) / mSwipeDistance);
161 mListener.onSwipe(mAmount);
162 }
163
164 private void fling(int screenY) {
165 mVelocityTracker.computeCurrentVelocity(1);
166
167 mVelocity = mVelocityTracker.getYVelocity() / mSwipeDistance;
168 mDirection = mVelocity >= 0.0f ? 1 : -1;
169 mAmount = clamp((screenY-mDownY)/mSwipeDistance);
170 if (mAmount < 0) {
171 mDest = clamp(mVelocity < 0 ? -1.0f : 0.0f);
172 } else {
173 mDest = clamp(mVelocity < 0 ? 0.0f : 1.0f);
174 }
175
176 mFlingTime = SystemClock.uptimeMillis();
177 mLastTime = 0;
178
179 scheduleAnim();
180 }
181
182 private void scheduleAnim() {
183 boolean send = true;
184 if (mDirection > 0) {
185 if (mAmount > (mDest - 0.01f)) {
186 send = false;
187 }
188 } else {
189 if (mAmount < (mDest + 0.01f)) {
190 send = false;
191 }
192 }
193 if (send) {
194 mHandler.sendEmptyMessageDelayed(1, FRAME_DELAY);
195 } else {
196 mListener.onFinishSwipe((int)(mAmount >= 0 ? (mAmount+0.5f) : (mAmount-0.5f)));
197 }
198 }
199
200 Handler mHandler = new Handler() {
201 public void handleMessage(Message msg) {
202 long now = SystemClock.uptimeMillis();
203
204 final long t = now - mFlingTime;
205 final long dt = t - mLastTime;
206 mLastTime = t;
207 final float timeSlices = dt / (float)FRAME_DELAY;
208
209 float distance = mDest - mAmount;
210
211 mVelocity += timeSlices * mDirection * SPRING_CONSTANT * distance * distance / 2;
212 mVelocity *= (timeSlices * DECAY_CONSTANT);
213
214 mAmount += timeSlices * mVelocity;
215 mAmount += distance * timeSlices * 0.2f; // cheat
216
217 mListener.onSwipe(mAmount);
218 scheduleAnim();
219 }
220 };
221}
222