NotificationStackScrollLayout.java revision fab078b01fbad026f006744016272327f7ab116b
1/* 2 * Copyright (C) 2014 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.systemui.statusbar.stack; 18 19import android.content.Context; 20import android.content.res.Configuration; 21 22import android.graphics.Canvas; 23import android.graphics.Outline; 24import android.graphics.Paint; 25import android.util.AttributeSet; 26import android.util.Log; 27 28import android.view.MotionEvent; 29import android.view.VelocityTracker; 30import android.view.View; 31import android.view.ViewConfiguration; 32import android.view.ViewGroup; 33import android.view.ViewParent; 34import android.widget.OverScroller; 35 36import com.android.systemui.ExpandHelper; 37import com.android.systemui.R; 38import com.android.systemui.SwipeHelper; 39import com.android.systemui.statusbar.ExpandableNotificationRow; 40 41/** 42 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. 43 */ 44public class NotificationStackScrollLayout extends ViewGroup 45 implements SwipeHelper.Callback, ExpandHelper.Callback, ExpandHelper.ScrollAdapter { 46 47 private static final String TAG = "NotificationStackScrollLayout"; 48 private static final boolean DEBUG = false; 49 50 /** 51 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. 52 */ 53 private static final int INVALID_POINTER = -1; 54 55 private SwipeHelper mSwipeHelper; 56 private boolean mAllowScrolling = true; 57 private int mCurrentStackHeight = Integer.MAX_VALUE; 58 private int mOwnScrollY; 59 private int mMaxLayoutHeight; 60 61 private VelocityTracker mVelocityTracker; 62 private OverScroller mScroller; 63 private int mTouchSlop; 64 private int mMinimumVelocity; 65 private int mMaximumVelocity; 66 private int mOverscrollDistance; 67 private int mOverflingDistance; 68 private boolean mIsBeingDragged; 69 private int mLastMotionY; 70 private int mActivePointerId; 71 72 private int mSidePaddings; 73 private Paint mDebugPaint; 74 private int mBackgroundRoundedRectCornerRadius; 75 private int mContentHeight; 76 private int mCollapsedSize; 77 private int mBottomStackPeekSize; 78 private int mEmptyMarginBottom; 79 private int mPaddingBetweenElements; 80 81 /** 82 * The algorithm which calculates the properties for our children 83 */ 84 private StackScrollAlgorithm mStackScrollAlgorithm; 85 86 /** 87 * The current State this Layout is in 88 */ 89 private StackScrollState mCurrentStackScrollState; 90 91 public NotificationStackScrollLayout(Context context) { 92 this(context, null); 93 } 94 95 public NotificationStackScrollLayout(Context context, AttributeSet attrs) { 96 this(context, attrs, 0); 97 } 98 99 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) { 100 this(context, attrs, defStyleAttr, 0); 101 } 102 103 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, 104 int defStyleRes) { 105 super(context, attrs, defStyleAttr, defStyleRes); 106 initView(context); 107 if (DEBUG) { 108 setWillNotDraw(false); 109 mDebugPaint = new Paint(); 110 mDebugPaint.setColor(0xffff0000); 111 mDebugPaint.setStrokeWidth(2); 112 mDebugPaint.setStyle(Paint.Style.STROKE); 113 } 114 } 115 116 @Override 117 protected void onDraw(Canvas canvas) { 118 if (DEBUG) { 119 int y = mCollapsedSize; 120 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 121 y = (int) (getLayoutHeight() - mBottomStackPeekSize - mCollapsedSize); 122 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 123 y = (int) getLayoutHeight(); 124 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 125 } 126 } 127 128 private void initView(Context context) { 129 mScroller = new OverScroller(getContext()); 130 setFocusable(true); 131 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 132 final ViewConfiguration configuration = ViewConfiguration.get(context); 133 mTouchSlop = configuration.getScaledTouchSlop(); 134 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 135 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 136 mOverscrollDistance = configuration.getScaledOverscrollDistance(); 137 mOverflingDistance = configuration.getScaledOverflingDistance(); 138 float densityScale = getResources().getDisplayMetrics().density; 139 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 140 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); 141 142 mSidePaddings = context.getResources() 143 .getDimensionPixelSize(R.dimen.notification_side_padding); 144 mBackgroundRoundedRectCornerRadius = context.getResources() 145 .getDimensionPixelSize( 146 com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); 147 mCollapsedSize = context.getResources() 148 .getDimensionPixelSize(R.dimen.notification_row_min_height); 149 mBottomStackPeekSize = context.getResources() 150 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount); 151 mEmptyMarginBottom = context.getResources().getDimensionPixelSize( 152 R.dimen.notification_stack_margin_bottom); 153 // currently the padding is in the elements themself 154 mPaddingBetweenElements = 0; 155 mStackScrollAlgorithm = new StackScrollAlgorithm(context); 156 mCurrentStackScrollState = null; 157 } 158 159 @Override 160 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 161 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 162 int mode = MeasureSpec.getMode(widthMeasureSpec); 163 int size = MeasureSpec.getSize(widthMeasureSpec); 164 int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode); 165 measureChildren(childMeasureSpec, heightMeasureSpec); 166 } 167 168 @Override 169 protected void onLayout(boolean changed, int l, int t, int r, int b) { 170 171 // we layout all our children centered on the top 172 float centerX = getWidth() / 2.0f; 173 for (int i = 0; i < getChildCount(); i++) { 174 View child = getChildAt(i); 175 float width = child.getMeasuredWidth(); 176 float height = child.getMeasuredHeight(); 177 int oldWidth = child.getWidth(); 178 int oldHeight = child.getHeight(); 179 child.layout((int) (centerX - width / 2.0f), 180 0, 181 (int) (centerX + width / 2.0f), 182 (int) height); 183 updateChildOutline(child, width, height, oldWidth, oldHeight); 184 } 185 setMaxLayoutHeight(getHeight() - mEmptyMarginBottom); 186 updateScrollPositionIfNecessary(); 187 updateChildren(); 188 updateContentHeight(); 189 } 190 191 private void setMaxLayoutHeight(int maxLayoutHeight) { 192 mMaxLayoutHeight = maxLayoutHeight; 193 updateAlgorithmHeight(); 194 } 195 196 private void updateAlgorithmHeight() { 197 mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight()); 198 } 199 200 /** 201 * Updates the children views according to the stack scroll algorithm. Call this whenever 202 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. 203 */ 204 private void updateChildren() { 205 if (!isCurrentlyAnimating()) { 206 if (mCurrentStackScrollState == null) { 207 mCurrentStackScrollState = new StackScrollState(this); 208 } 209 mCurrentStackScrollState.setScrollY(mOwnScrollY); 210 mStackScrollAlgorithm.getStackScrollState(mCurrentStackScrollState); 211 mCurrentStackScrollState.apply(); 212 mOwnScrollY = mCurrentStackScrollState.getScrollY(); 213 } else { 214 // TODO: handle animation 215 } 216 } 217 218 private boolean isCurrentlyAnimating() { 219 return false; 220 } 221 222 private void updateChildOutline(View child, 223 float width, 224 float height, 225 int oldWidth, 226 int oldHeight) { 227 // The children currently have paddings inside themselfs because of the expansion 228 // visualization. In order for the shadows to work correctly we have to set the correct 229 // outline. 230 View container = child.findViewById(R.id.container); 231 if (container != null && (oldWidth != width || oldHeight != height)) { 232 Outline outline = getOutlineForSize(container.getLeft(), 233 container.getTop(), 234 container.getWidth(), 235 container.getHeight()); 236 child.setOutline(outline); 237 } 238 } 239 240 private Outline getOutlineForSize(int leftInset, int topInset, int width, int height) { 241 Outline result = new Outline(); 242 result.setRoundRect(leftInset, topInset, leftInset + width, topInset + height, 243 mBackgroundRoundedRectCornerRadius); 244 return result; 245 } 246 247 private void updateScrollPositionIfNecessary() { 248 int scrollRange = getScrollRange(); 249 if (scrollRange < mOwnScrollY) { 250 mOwnScrollY = scrollRange; 251 } 252 } 253 254 public void setCurrentStackHeight(int currentStackHeight) { 255 this.mCurrentStackHeight = currentStackHeight; 256 updateAlgorithmHeight(); 257 updateChildren(); 258 } 259 260 /** 261 * Get the current height of the view. This is at most the size of the view given by a the 262 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight} 263 * 264 * @return either the layout height or the externally defined height, whichever is smaller 265 */ 266 private float getLayoutHeight() { 267 return Math.min(mMaxLayoutHeight, mCurrentStackHeight); 268 } 269 270 public void setLongPressListener(View.OnLongClickListener listener) { 271 mSwipeHelper.setLongPressListener(listener); 272 } 273 274 public void onChildDismissed(View v) { 275 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v); 276 final View veto = v.findViewById(R.id.veto); 277 if (veto != null && veto.getVisibility() != View.GONE) { 278 veto.performClick(); 279 } 280 allowScrolling(true); 281 } 282 283 public void onBeginDrag(View v) { 284 allowScrolling(false); 285 } 286 287 public void onDragCancelled(View v) { 288 allowScrolling(true); 289 } 290 291 public View getChildAtPosition(MotionEvent ev) { 292 return getChildAtPosition(ev.getX(), ev.getY()); 293 } 294 295 public View getChildAtRawPosition(float touchX, float touchY) { 296 int[] location = new int[2]; 297 getLocationOnScreen(location); 298 return getChildAtPosition(touchX - location[0],touchY - location[1]); 299 } 300 301 public View getChildAtPosition(float touchX, float touchY) { 302 // find the view under the pointer, accounting for GONE views 303 final int count = getChildCount(); 304 for (int childIdx = 0; childIdx < count; childIdx++) { 305 View slidingChild = getChildAt(childIdx); 306 if (slidingChild.getVisibility() == GONE) { 307 continue; 308 } 309 float top = slidingChild.getTranslationY(); 310 float bottom = top + slidingChild.getMeasuredHeight(); 311 int left = slidingChild.getLeft(); 312 int right = slidingChild.getRight(); 313 314 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { 315 return slidingChild; 316 } 317 } 318 return null; 319 } 320 321 public boolean canChildBeExpanded(View v) { 322 return v instanceof ExpandableNotificationRow 323 && ((ExpandableNotificationRow) v).isExpandable(); 324 } 325 326 public void setUserExpandedChild(View v, boolean userExpanded) { 327 if (v instanceof ExpandableNotificationRow) { 328 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded); 329 } 330 } 331 332 public void setUserLockedChild(View v, boolean userLocked) { 333 if (v instanceof ExpandableNotificationRow) { 334 ((ExpandableNotificationRow) v).setUserLocked(userLocked); 335 } 336 } 337 338 public View getChildContentView(View v) { 339 return v; 340 } 341 342 public boolean canChildBeDismissed(View v) { 343 final View veto = v.findViewById(R.id.veto); 344 return (veto != null && veto.getVisibility() != View.GONE); 345 } 346 347 private void allowScrolling(boolean allow) { 348 mAllowScrolling = allow; 349 } 350 351 @Override 352 protected void onConfigurationChanged(Configuration newConfig) { 353 super.onConfigurationChanged(newConfig); 354 float densityScale = getResources().getDisplayMetrics().density; 355 mSwipeHelper.setDensityScale(densityScale); 356 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 357 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 358 initView(getContext()); 359 } 360 361 public void dismissRowAnimated(View child, int vel) { 362 mSwipeHelper.dismissChild(child, vel); 363 } 364 365 @Override 366 public boolean onTouchEvent(MotionEvent ev) { 367 boolean scrollerWantsIt = false; 368 if (mAllowScrolling) { 369 scrollerWantsIt = onScrollTouch(ev); 370 } 371 boolean horizontalSwipeWantsIt = false; 372 if (!mIsBeingDragged) { 373 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); 374 } 375 return horizontalSwipeWantsIt || scrollerWantsIt || super.onTouchEvent(ev); 376 } 377 378 private boolean onScrollTouch(MotionEvent ev) { 379 initVelocityTrackerIfNotExists(); 380 mVelocityTracker.addMovement(ev); 381 382 final int action = ev.getAction(); 383 384 switch (action & MotionEvent.ACTION_MASK) { 385 case MotionEvent.ACTION_DOWN: { 386 if (getChildCount() == 0) { 387 return false; 388 } 389 boolean isBeingDragged = !mScroller.isFinished(); 390 setIsBeingDragged(isBeingDragged); 391 if (isBeingDragged) { 392 final ViewParent parent = getParent(); 393 if (parent != null) { 394 parent.requestDisallowInterceptTouchEvent(true); 395 } 396 } 397 398 /* 399 * If being flinged and user touches, stop the fling. isFinished 400 * will be false if being flinged. 401 */ 402 if (!mScroller.isFinished()) { 403 mScroller.abortAnimation(); 404 } 405 406 // Remember where the motion event started 407 mLastMotionY = (int) ev.getY(); 408 mActivePointerId = ev.getPointerId(0); 409 break; 410 } 411 case MotionEvent.ACTION_MOVE: 412 final int activePointerIndex = ev.findPointerIndex(mActivePointerId); 413 if (activePointerIndex == -1) { 414 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); 415 break; 416 } 417 418 final int y = (int) ev.getY(activePointerIndex); 419 int deltaY = mLastMotionY - y; 420 if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { 421 final ViewParent parent = getParent(); 422 if (parent != null) { 423 parent.requestDisallowInterceptTouchEvent(true); 424 } 425 setIsBeingDragged(true); 426 if (deltaY > 0) { 427 deltaY -= mTouchSlop; 428 } else { 429 deltaY += mTouchSlop; 430 } 431 } 432 if (mIsBeingDragged) { 433 // Scroll to follow the motion event 434 mLastMotionY = y; 435 436 final int oldX = mScrollX; 437 final int oldY = mOwnScrollY; 438 final int range = getScrollRange(); 439 final int overscrollMode = getOverScrollMode(); 440 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || 441 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); 442 443 // Calling overScrollBy will call onOverScrolled, which 444 // calls onScrollChanged if applicable. 445 if (overScrollBy(0, deltaY, 0, mOwnScrollY, 446 0, range, 0, mOverscrollDistance, true)) { 447 // Break our velocity if we hit a scroll barrier. 448 mVelocityTracker.clear(); 449 } 450 // TODO: Overscroll 451// if (canOverscroll) { 452// final int pulledToY = oldY + deltaY; 453// if (pulledToY < 0) { 454// mEdgeGlowTop.onPull((float) deltaY / getHeight()); 455// if (!mEdgeGlowBottom.isFinished()) { 456// mEdgeGlowBottom.onRelease(); 457// } 458// } else if (pulledToY > range) { 459// mEdgeGlowBottom.onPull((float) deltaY / getHeight()); 460// if (!mEdgeGlowTop.isFinished()) { 461// mEdgeGlowTop.onRelease(); 462// } 463// } 464// if (mEdgeGlowTop != null 465// && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())){ 466// postInvalidateOnAnimation(); 467// } 468// } 469 } 470 break; 471 case MotionEvent.ACTION_UP: 472 if (mIsBeingDragged) { 473 final VelocityTracker velocityTracker = mVelocityTracker; 474 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 475 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); 476 477 if (getChildCount() > 0) { 478 if ((Math.abs(initialVelocity) > mMinimumVelocity)) { 479 fling(-initialVelocity); 480 } else { 481 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, 482 getScrollRange())) { 483 postInvalidateOnAnimation(); 484 } 485 } 486 } 487 488 mActivePointerId = INVALID_POINTER; 489 endDrag(); 490 } 491 break; 492 case MotionEvent.ACTION_CANCEL: 493 if (mIsBeingDragged && getChildCount() > 0) { 494 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 495 postInvalidateOnAnimation(); 496 } 497 mActivePointerId = INVALID_POINTER; 498 endDrag(); 499 } 500 break; 501 case MotionEvent.ACTION_POINTER_DOWN: { 502 final int index = ev.getActionIndex(); 503 mLastMotionY = (int) ev.getY(index); 504 mActivePointerId = ev.getPointerId(index); 505 break; 506 } 507 case MotionEvent.ACTION_POINTER_UP: 508 onSecondaryPointerUp(ev); 509 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); 510 break; 511 } 512 return true; 513 } 514 515 private void onSecondaryPointerUp(MotionEvent ev) { 516 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 517 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 518 final int pointerId = ev.getPointerId(pointerIndex); 519 if (pointerId == mActivePointerId) { 520 // This was our active pointer going up. Choose a new 521 // active pointer and adjust accordingly. 522 // TODO: Make this decision more intelligent. 523 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 524 mLastMotionY = (int) ev.getY(newPointerIndex); 525 mActivePointerId = ev.getPointerId(newPointerIndex); 526 if (mVelocityTracker != null) { 527 mVelocityTracker.clear(); 528 } 529 } 530 } 531 532 private void initVelocityTrackerIfNotExists() { 533 if (mVelocityTracker == null) { 534 mVelocityTracker = VelocityTracker.obtain(); 535 } 536 } 537 538 private void recycleVelocityTracker() { 539 if (mVelocityTracker != null) { 540 mVelocityTracker.recycle(); 541 mVelocityTracker = null; 542 } 543 } 544 545 private void initOrResetVelocityTracker() { 546 if (mVelocityTracker == null) { 547 mVelocityTracker = VelocityTracker.obtain(); 548 } else { 549 mVelocityTracker.clear(); 550 } 551 } 552 553 @Override 554 public void computeScroll() { 555 if (mScroller.computeScrollOffset()) { 556 // This is called at drawing time by ViewGroup. 557 int oldX = mScrollX; 558 int oldY = mOwnScrollY; 559 int x = mScroller.getCurrX(); 560 int y = mScroller.getCurrY(); 561 562 if (oldX != x || oldY != y) { 563 final int range = getScrollRange(); 564 final int overscrollMode = getOverScrollMode(); 565 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || 566 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); 567 568 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range, 569 0, mOverflingDistance, false); 570 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); 571 572 if (canOverscroll) { 573 // TODO: Overscroll 574// if (y < 0 && oldY >= 0) { 575// mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); 576// } else if (y > range && oldY <= range) { 577// mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); 578// } 579 } 580 updateChildren(); 581 } 582 583 // Keep on drawing until the animation has finished. 584 postInvalidateOnAnimation(); 585 } 586 } 587 588 public void customScrollBy(int y) { 589 mOwnScrollY += y; 590 updateChildren(); 591 } 592 593 public void customScrollTo(int y) { 594 mOwnScrollY = y; 595 updateChildren(); 596 } 597 598 @Override 599 protected void onOverScrolled(int scrollX, int scrollY, 600 boolean clampedX, boolean clampedY) { 601 // Treat animating scrolls differently; see #computeScroll() for why. 602 if (!mScroller.isFinished()) { 603 final int oldX = mScrollX; 604 final int oldY = mOwnScrollY; 605 mScrollX = scrollX; 606 mOwnScrollY = scrollY; 607 invalidateParentIfNeeded(); 608 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); 609 if (clampedY) { 610 mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange()); 611 } 612 updateChildren(); 613 } else { 614 customScrollTo(scrollY); 615 scrollTo(scrollX, mScrollY); 616 } 617 } 618 619 private int getScrollRange() { 620 int scrollRange = 0; 621 if (getChildCount() > 0) { 622 int contentHeight = getContentHeight(); 623 scrollRange = Math.max(0, 624 contentHeight - mMaxLayoutHeight + mCollapsedSize); 625 } 626 return scrollRange; 627 } 628 629 private int getContentHeight() { 630 return mContentHeight; 631 } 632 633 private void updateContentHeight() { 634 int height = 0; 635 for (int i = 0; i < getChildCount(); i++) { 636 View child = getChildAt(i); 637 height += child.getHeight(); 638 if (i < getChildCount()-1) { 639 height += mPaddingBetweenElements; 640 } 641 } 642 mContentHeight = height; 643 } 644 645 /** 646 * Fling the scroll view 647 * 648 * @param velocityY The initial velocity in the Y direction. Positive 649 * numbers mean that the finger/cursor is moving down the screen, 650 * which means we want to scroll towards the top. 651 */ 652 private void fling(int velocityY) { 653 if (getChildCount() > 0) { 654 int height = (int) getLayoutHeight(); 655 int bottom = getContentHeight(); 656 657 mScroller.fling(mScrollX, mOwnScrollY, 0, velocityY, 0, 0, 0, 658 Math.max(0, bottom - height), 0, height/2); 659 660 postInvalidateOnAnimation(); 661 } 662 } 663 664 private void endDrag() { 665 setIsBeingDragged(false); 666 667 recycleVelocityTracker(); 668 669 // TODO: Overscroll 670// if (mEdgeGlowTop != null) { 671// mEdgeGlowTop.onRelease(); 672// mEdgeGlowBottom.onRelease(); 673// } 674 } 675 676 @Override 677 public boolean onInterceptTouchEvent(MotionEvent ev) { 678 boolean scrollWantsIt = false; 679 if (mAllowScrolling) { 680 scrollWantsIt = onInterceptTouchEventScroll(ev); 681 } 682 boolean swipeWantsIt = false; 683 if (!mIsBeingDragged) { 684 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); 685 } 686 return swipeWantsIt || scrollWantsIt || 687 super.onInterceptTouchEvent(ev); 688 } 689 690 private boolean onInterceptTouchEventScroll(MotionEvent ev) { 691 /* 692 * This method JUST determines whether we want to intercept the motion. 693 * If we return true, onMotionEvent will be called and we do the actual 694 * scrolling there. 695 */ 696 697 /* 698 * Shortcut the most recurring case: the user is in the dragging 699 * state and he is moving his finger. We want to intercept this 700 * motion. 701 */ 702 final int action = ev.getAction(); 703 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { 704 return true; 705 } 706 707 /* 708 * Don't try to intercept touch if we can't scroll anyway. 709 */ 710 if (mOwnScrollY == 0 && getScrollRange() == 0) { 711 return false; 712 } 713 714 switch (action & MotionEvent.ACTION_MASK) { 715 case MotionEvent.ACTION_MOVE: { 716 /* 717 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 718 * whether the user has moved far enough from his original down touch. 719 */ 720 721 /* 722 * Locally do absolute value. mLastMotionY is set to the y value 723 * of the down event. 724 */ 725 final int activePointerId = mActivePointerId; 726 if (activePointerId == INVALID_POINTER) { 727 // If we don't have a valid id, the touch down wasn't on content. 728 break; 729 } 730 731 final int pointerIndex = ev.findPointerIndex(activePointerId); 732 if (pointerIndex == -1) { 733 Log.e(TAG, "Invalid pointerId=" + activePointerId 734 + " in onInterceptTouchEvent"); 735 break; 736 } 737 738 final int y = (int) ev.getY(pointerIndex); 739 final int yDiff = Math.abs(y - mLastMotionY); 740 if (yDiff > mTouchSlop) { 741 setIsBeingDragged(true); 742 mLastMotionY = y; 743 initVelocityTrackerIfNotExists(); 744 mVelocityTracker.addMovement(ev); 745 final ViewParent parent = getParent(); 746 if (parent != null) { 747 parent.requestDisallowInterceptTouchEvent(true); 748 } 749 } 750 break; 751 } 752 753 case MotionEvent.ACTION_DOWN: { 754 final int y = (int) ev.getY(); 755 if (getChildAtPosition(ev.getX(), y) == null) { 756 setIsBeingDragged(false); 757 recycleVelocityTracker(); 758 break; 759 } 760 761 /* 762 * Remember location of down touch. 763 * ACTION_DOWN always refers to pointer index 0. 764 */ 765 mLastMotionY = y; 766 mActivePointerId = ev.getPointerId(0); 767 768 initOrResetVelocityTracker(); 769 mVelocityTracker.addMovement(ev); 770 /* 771 * If being flinged and user touches the screen, initiate drag; 772 * otherwise don't. mScroller.isFinished should be false when 773 * being flinged. 774 */ 775 boolean isBeingDragged = !mScroller.isFinished(); 776 setIsBeingDragged(isBeingDragged); 777 break; 778 } 779 780 case MotionEvent.ACTION_CANCEL: 781 case MotionEvent.ACTION_UP: 782 /* Release the drag */ 783 setIsBeingDragged(false); 784 mActivePointerId = INVALID_POINTER; 785 recycleVelocityTracker(); 786 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 787 postInvalidateOnAnimation(); 788 } 789 break; 790 case MotionEvent.ACTION_POINTER_UP: 791 onSecondaryPointerUp(ev); 792 break; 793 } 794 795 /* 796 * The only time we want to intercept motion events is if we are in the 797 * drag mode. 798 */ 799 return mIsBeingDragged; 800 } 801 802 private void setIsBeingDragged(boolean isDragged) { 803 mIsBeingDragged = isDragged; 804 if (isDragged) { 805 mSwipeHelper.removeLongPressCallback(); 806 } 807 } 808 809 @Override 810 public void onWindowFocusChanged(boolean hasWindowFocus) { 811 super.onWindowFocusChanged(hasWindowFocus); 812 if (!hasWindowFocus) { 813 mSwipeHelper.removeLongPressCallback(); 814 } 815 } 816 817 @Override 818 public boolean isScrolledToTop() { 819 return mOwnScrollY == 0; 820 } 821 822 @Override 823 public View getHostView() { 824 return this; 825 } 826} 827