001 |
@Override |
002 |
public boolean onTouchEvent(MotionEvent ev) { |
003 |
if (!isEnabled()) { |
004 |
// A disabled view that is clickable still consumes the touch |
005 |
// events, it just doesn't respond to them. |
006 |
return isClickable() || isLongClickable(); |
007 |
} |
008 |
if (mFastScroller != null ) { |
009 |
boolean intercepted = mFastScroller.onTouchEvent(ev); |
010 |
if (intercepted) { |
011 |
return true ; |
012 |
} |
013 |
} |
014 |
final int action = ev.getAction(); |
015 |
View v; |
016 |
int deltaY; |
017 |
if (mVelocityTracker == null ) { |
018 |
mVelocityTracker = VelocityTracker.obtain(); |
019 |
} |
020 |
mVelocityTracker.addMovement(ev); |
021 |
switch (action & MotionEvent.ACTION_MASK) { |
022 |
case MotionEvent.ACTION_DOWN: { |
023 |
setVerticalFadingEdgeEnabled( false ); |
024 |
mActivePointerId = ev.getPointerId( 0 ); |
025 |
final int x = ( int ) ev.getX(); |
026 |
final int y = ( int ) ev.getY(); |
027 |
int motionPosition = pointToPosition(x, y); |
028 |
if (!mDataChanged) { |
029 |
if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0 ) |
030 |
&& (getAdapter().isEnabled(motionPosition))) { |
031 |
// User clicked on an actual view (and was not stopping a fling). It might be a |
032 |
// click or a scroll. Assume it is a click until proven otherwise |
033 |
mTouchMode = TOUCH_MODE_DOWN; |
034 |
// FIXME Debounce |
035 |
if (mPendingCheckForTap == null ) { |
036 |
mPendingCheckForTap = new CheckForTap(); |
037 |
} |
038 |
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); |
039 |
} else { |
040 |
if (ev.getEdgeFlags() != 0 && motionPosition < 0 ) { |
041 |
// If we couldn't find a view to click on, but the down event was touching |
042 |
// the edge, we will bail out and try again. This allows the edge correcting |
043 |
// code in ViewRoot to try to find a nearby view to select |
044 |
return false ; |
045 |
} |
046 |
if (mTouchMode == TOUCH_MODE_FLING) { |
047 |
// Stopped a fling. It is a scroll. |
048 |
createScrollingCache(); |
049 |
mTouchMode = TOUCH_MODE_SCROLL; |
050 |
mMotionCorrection = 0 ; |
051 |
motionPosition = findMotionRow(y); |
052 |
reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); |
053 |
} |
054 |
} |
055 |
} |
056 |
if (motionPosition >= 0 ) { |
057 |
// Remember where the motion event started |
058 |
v = getChildAt(motionPosition - mFirstPosition); |
059 |
mMotionViewOriginalTop = v.getTop(); |
060 |
} |
061 |
mMotionX = x; |
062 |
mMotionY = y; |
063 |
mMotionPosition = motionPosition; |
064 |
mLastY = Integer.MIN_VALUE; |
065 |
break ; |
066 |
} |
067 |
case MotionEvent.ACTION_MOVE: { |
068 |
final int pointerIndex = ev.findPointerIndex(mActivePointerId); |
069 |
final int y = ( int ) ev.getY(pointerIndex); |
070 |
deltaY = y - mMotionY; |
071 |
switch (mTouchMode) { |
072 |
case TOUCH_MODE_DOWN: |
073 |
case TOUCH_MODE_TAP: |
074 |
case TOUCH_MODE_DONE_WAITING: |
075 |
// Check if we have moved far enough that it looks more like a |
076 |
// scroll than a tap |
077 |
startScrollIfNeeded(deltaY); |
078 |
break ; |
079 |
case TOUCH_MODE_SCROLL: |
080 |
if (PROFILE_SCROLLING) { |
081 |
if (!mScrollProfilingStarted) { |
082 |
Debug.startMethodTracing( "AbsListViewScroll" ); |
083 |
mScrollProfilingStarted = true ; |
084 |
} |
085 |
} |
086 |
if (y != mLastY) { |
087 |
deltaY -= mMotionCorrection; |
088 |
int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; |
089 |
|
090 |
// No need to do all this work if we're not going to move anyway |
091 |
boolean atEdge = false ; |
092 |
if (incrementalDeltaY != 0 ) { |
093 |
atEdge = trackMotionScroll(deltaY, incrementalDeltaY); |
094 |
} |
095 |
// Check to see if we have bumped into the scroll limit |
096 |
if (atEdge && getChildCount() > 0 ) { |
097 |
// Treat this like we're starting a new scroll from the current |
098 |
// position. This will let the user start scrolling back into |
099 |
// content immediately rather than needing to scroll back to the |
100 |
// point where they hit the limit first. |
101 |
int motionPosition = findMotionRow(y); |
102 |
if (motionPosition >= 0 ) { |
103 |
final View motionView = getChildAt(motionPosition - mFirstPosition); |
104 |
mMotionViewOriginalTop = motionView.getTop(); |
105 |
} |
106 |
mMotionY = y; |
107 |
mMotionPosition = motionPosition; |
108 |
invalidate(); |
109 |
} |
110 |
mLastY = y; |
111 |
} |
112 |
break ; |
113 |
} |
114 |
break ; |
115 |
} |
116 |
case MotionEvent.ACTION_UP: { |
117 |
switch (mTouchMode) { |
118 |
case TOUCH_MODE_DOWN: |
119 |
case TOUCH_MODE_TAP: |
120 |
case TOUCH_MODE_DONE_WAITING: |
121 |
setVerticalFadingEdgeEnabled( true ); |
122 |
final int motionPosition = mMotionPosition; |
123 |
final View child = getChildAt(motionPosition - mFirstPosition); |
124 |
if (child != null && !child.hasFocusable()) { |
125 |
if (mTouchMode != TOUCH_MODE_DOWN) { |
126 |
child.setPressed( false ); |
127 |
} |
128 |
if (mPerformClick == null ) { |
129 |
mPerformClick = new PerformClick(); |
130 |
} |
131 |
final AbsListView.PerformClick performClick = mPerformClick; |
132 |
performClick.mChild = child; |
133 |
performClick.mClickMotionPosition = motionPosition; |
134 |
performClick.rememberWindowAttachCount(); |
135 |
mResurrectToPosition = motionPosition; |
136 |
if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { |
137 |
final Handler handler = getHandler(); |
138 |
if (handler != null ) { |
139 |
handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? |
140 |
mPendingCheckForTap : mPendingCheckForLongPress); |
141 |
} |
142 |
mLayoutMode = LAYOUT_NORMAL; |
143 |
if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { |
144 |
mTouchMode = TOUCH_MODE_TAP; |
145 |
setSelectedPositionInt(mMotionPosition); |
146 |
layoutChildren(); |
147 |
child.setPressed( true ); |
148 |
positionSelector(child); |
149 |
setPressed( true ); |
150 |
if (mSelector != null ) { |
151 |
Drawable d = mSelector.getCurrent(); |
152 |
if (d != null && d instanceof TransitionDrawable) { |
153 |
((TransitionDrawable) d).resetTransition(); |
154 |
} |
155 |
} |
156 |
postDelayed( new Runnable() { |
157 |
public void run() { |
158 |
child.setPressed( false ); |
159 |
setPressed( false ); |
160 |
if (!mDataChanged) { |
161 |
post(performClick); |
162 |
} |
163 |
mTouchMode = TOUCH_MODE_REST; |
164 |
} |
165 |
}, ViewConfiguration.getPressedStateDuration()); |
166 |
} else { |
167 |
mTouchMode = TOUCH_MODE_REST; |
168 |
} |
169 |
return true ; |
170 |
} else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { |
171 |
post(performClick); |
172 |
} |
173 |
} |
174 |
mTouchMode = TOUCH_MODE_REST; |
175 |
break ; |
176 |
case TOUCH_MODE_SCROLL: |
177 |
final int childCount = getChildCount(); |
178 |
if (childCount > 0 ) { |
179 |
if (mFirstPosition == 0 && getChildAt( 0 ).getTop() >= mListPadding.top && |
180 |
mFirstPosition + childCount < mItemCount && |
181 |
getChildAt(childCount - 1 ).getBottom() <= |
182 |
getHeight() - mListPadding.bottom) { |
183 |
mTouchMode = TOUCH_MODE_REST; |
184 |
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
185 |
setVerticalFadingEdgeEnabled( true ); |
186 |
} else { |
187 |
final VelocityTracker velocityTracker = mVelocityTracker; |
188 |
velocityTracker.computeCurrentVelocity( 1000 , mMaximumVelocity); |
189 |
final int initialVelocity = ( int ) velocityTracker.getYVelocity(mActivePointerId); |
190 |
|
191 |
if (Math.abs(initialVelocity) > mMinimumVelocity) { |
192 |
if (mFlingRunnable == null ) { |
193 |
mFlingRunnable = new FlingRunnable(); |
194 |
} |
195 |
reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); |
196 |
|
197 |
mFlingRunnable.start(-initialVelocity); |
198 |
} else { |
199 |
mTouchMode = TOUCH_MODE_REST; |
200 |
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
201 |
setVerticalFadingEdgeEnabled( true ); |
202 |
} |
203 |
} |
204 |
} else { |
205 |
mTouchMode = TOUCH_MODE_REST; |
206 |
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
207 |
setVerticalFadingEdgeEnabled( true ); |
208 |
} |
209 |
break ; |
210 |
} |
211 |
setPressed( false ); |
212 |
// Need to redraw since we probably aren't drawing the selector anymore |
213 |
invalidate(); |
214 |
final Handler handler = getHandler(); |
215 |
if (handler != null ) { |
216 |
handler.removeCallbacks(mPendingCheckForLongPress); |
217 |
} |
218 |
if (mVelocityTracker != null ) { |
219 |
mVelocityTracker.recycle(); |
220 |
mVelocityTracker = null ; |
221 |
} |
222 |
|
223 |
mActivePointerId = INVALID_POINTER; |
224 |
if (PROFILE_SCROLLING) { |
225 |
if (mScrollProfilingStarted) { |
226 |
Debug.stopMethodTracing(); |
227 |
mScrollProfilingStarted = false ; |
228 |
} |
229 |
} |
230 |
break ; |
231 |
} |
232 |
case MotionEvent.ACTION_CANCEL: { |
233 |
mTouchMode = TOUCH_MODE_REST; |
234 |
setPressed( false ); |
235 |
View motionView = this .getChildAt(mMotionPosition - mFirstPosition); |
236 |
if (motionView != null ) { |
237 |
motionView.setPressed( false ); |
238 |
} |
239 |
clearScrollingCache(); |
240 |
final Handler handler = getHandler(); |
241 |
if (handler != null ) { |
242 |
handler.removeCallbacks(mPendingCheckForLongPress); |
243 |
} |
244 |
if (mVelocityTracker != null ) { |
245 |
mVelocityTracker.recycle(); |
246 |
mVelocityTracker = null ; |
247 |
} |
248 |
|
249 |
mActivePointerId = INVALID_POINTER; |
250 |
break ; |
251 |
} |
252 |
|
253 |
case MotionEvent.ACTION_POINTER_UP: { |
254 |
onSecondaryPointerUp(ev); |
255 |
final int x = mMotionX; |
256 |
final int y = mMotionY; |
257 |
final int motionPosition = pointToPosition(x, y); |
258 |
if (motionPosition >= 0 ) { |
259 |
// Remember where the motion event started |
260 |
v = getChildAt(motionPosition - mFirstPosition); |
261 |
mMotionViewOriginalTop = v.getTop(); |
262 |
mMotionPosition = motionPosition; |
263 |
} |
264 |
mLastY = y; |
265 |
break ; |
266 |
} |
267 |
} |
268 |
return true ; |
269 |
} |
270 |
======================================================================== |
271 |
private class FlingRunnable implements Runnable { |
272 |
|
273 |
private final Scroller mScroller; |
274 |
|
275 |
private int mLastFlingY; |
276 |
FlingRunnable() { |
277 |
mScroller = new Scroller(getContext()); |
278 |
} |
279 |
void start( int initialVelocity) { |
280 |
int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0 ; |
281 |
mLastFlingY = initialY; |
282 |
mScroller.fling( 0 , initialY, 0 , initialVelocity, |
283 |
0 , Integer.MAX_VALUE, 0 , Integer.MAX_VALUE); |
284 |
mTouchMode = TOUCH_MODE_FLING; |
285 |
post( this ); |
286 |
if (PROFILE_FLINGING) { |
287 |
if (!mFlingProfilingStarted) { |
288 |
Debug.startMethodTracing( "AbsListViewFling" ); |
289 |
mFlingProfilingStarted = true ; |
290 |
} |
291 |
} |
292 |
} |
293 |
void startScroll( int distance, int duration) { |
294 |
int initialY = distance < 0 ? Integer.MAX_VALUE : 0 ; |
295 |
mLastFlingY = initialY; |
296 |
mScroller.startScroll( 0 , initialY, 0 , distance, duration); |
297 |
mTouchMode = TOUCH_MODE_FLING; |
298 |
post( this ); |
299 |
} |
300 |
private void endFling() { |
301 |
mTouchMode = TOUCH_MODE_REST; |
302 |
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
303 |
clearScrollingCache(); |
304 |
removeCallbacks( this ); |
305 |
if (mPositionScroller != null ) { |
306 |
removeCallbacks(mPositionScroller); |
307 |
} |
308 |
} |
309 |
public void run() { |
310 |
switch (mTouchMode) { |
311 |
default : |
312 |
return ; |
313 |
|
314 |
case TOUCH_MODE_FLING: { |
315 |
if (mItemCount == 0 || getChildCount() == 0 ) { |
316 |
endFling(); |
317 |
return ; |
318 |
} |
319 |
final Scroller scroller = mScroller; |
320 |
boolean more = scroller.computeScrollOffset(); |
321 |
final int y = scroller.getCurrY(); |
322 |
// Flip sign to convert finger direction to list items direction |
323 |
// (e.g. finger moving down means list is moving towards the top) |
324 |
int delta = mLastFlingY - y; |
325 |
// Pretend that each frame of a fling scroll is a touch scroll |
326 |
if (delta > 0 ) { |
327 |
// List is moving towards the top. Use first view as mMotionPosition |
328 |
mMotionPosition = mFirstPosition; |
329 |
final View firstView = getChildAt( 0 ); |
330 |
mMotionViewOriginalTop = firstView.getTop(); |
331 |
// Don't fling more than 1 screen |
332 |
delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1 , delta); |
333 |
} else { |
334 |
// List is moving towards the bottom. Use last view as mMotionPosition |
335 |
int offsetToLast = getChildCount() - 1 ; |
336 |
mMotionPosition = mFirstPosition + offsetToLast; |
337 |
final View lastView = getChildAt(offsetToLast); |
338 |
mMotionViewOriginalTop = lastView.getTop(); |
339 |
// Don't fling more than 1 screen |
340 |
delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1 ), delta); |
341 |
} |
342 |
final boolean atEnd = trackMotionScroll(delta, delta); |
343 |
if (more && !atEnd) { |
344 |
invalidate(); |
345 |
mLastFlingY = y; |
346 |
post( this ); |
347 |
} else { |
348 |
endFling(); |
349 |
AbsListView. this .setVerticalFadingEdgeEnabled( true ); |
350 |
if (PROFILE_FLINGING) { |
351 |
if (mFlingProfilingStarted) { |
352 |
Debug.stopMethodTracing(); |
353 |
mFlingProfilingStarted = false ; |
354 |
} |
355 |
} |
356 |
} |
357 |
break ; |
358 |
} |
359 |
} |
360 |
} |
361 |
} |