This class provides a platform version-independent mechanism for obeying the 26 | * current device's preferred scroll physics and fling behavior. It offers a subset of 27 | * the APIs from Scroller or OverScroller.
28 | */ 29 | class ScrollerCompat { 30 | Scroller mScroller; 31 | 32 | static class ScrollerCompatImplIcs extends ScrollerCompat { 33 | public ScrollerCompatImplIcs(Context context) { 34 | super(context); 35 | } 36 | 37 | @Override 38 | public float getCurrVelocity() { 39 | return ScrollerCompatIcs.getCurrVelocity(mScroller); 40 | } 41 | } 42 | 43 | public static ScrollerCompat from(Context context) { 44 | if (android.os.Build.VERSION.SDK_INT >= 14) { 45 | return new ScrollerCompatImplIcs(context); 46 | } 47 | return new ScrollerCompat(context); 48 | } 49 | 50 | ScrollerCompat(Context context) { 51 | mScroller = new Scroller(context); 52 | } 53 | 54 | /** 55 | * Returns whether the scroller has finished scrolling. 56 | * 57 | * @return True if the scroller has finished scrolling, false otherwise. 58 | */ 59 | public boolean isFinished() { 60 | return mScroller.isFinished(); 61 | } 62 | 63 | /** 64 | * Returns how long the scroll event will take, in milliseconds. 65 | * 66 | * @return The duration of the scroll in milliseconds. 67 | */ 68 | public int getDuration() { 69 | return mScroller.getDuration(); 70 | } 71 | 72 | /** 73 | * Returns the current X offset in the scroll. 74 | * 75 | * @return The new X offset as an absolute distance from the origin. 76 | */ 77 | public int getCurrX() { 78 | return mScroller.getCurrX(); 79 | } 80 | 81 | /** 82 | * Returns the current Y offset in the scroll. 83 | * 84 | * @return The new Y offset as an absolute distance from the origin. 85 | */ 86 | public int getCurrY() { 87 | return mScroller.getCurrY(); 88 | } 89 | 90 | /** 91 | * Returns the current velocity. 92 | * 93 | * TODO: Approximate a sane result for older platform versions. Right now 94 | * this will return 0 for platforms earlier than ICS. This is acceptable 95 | * at the moment only since it is only used for EdgeEffect, which is also only 96 | * present in ICS+, and ScrollerCompat is not public. 97 | * 98 | * @return The original velocity less the deceleration. Result may be 99 | * negative. 100 | */ 101 | public float getCurrVelocity() { 102 | return 0; 103 | } 104 | 105 | /** 106 | * Call this when you want to know the new location. If it returns true, 107 | * the animation is not yet finished. loc will be altered to provide the 108 | * new location. 109 | */ 110 | public boolean computeScrollOffset() { 111 | return mScroller.computeScrollOffset(); 112 | } 113 | 114 | /** 115 | * Start scrolling by providing a starting point and the distance to travel. 116 | * The scroll will use the default value of 250 milliseconds for the 117 | * duration. 118 | * 119 | * @param startX Starting horizontal scroll offset in pixels. Positive 120 | * numbers will scroll the content to the left. 121 | * @param startY Starting vertical scroll offset in pixels. Positive numbers 122 | * will scroll the content up. 123 | * @param dx Horizontal distance to travel. Positive numbers will scroll the 124 | * content to the left. 125 | * @param dy Vertical distance to travel. Positive numbers will scroll the 126 | * content up. 127 | */ 128 | public void startScroll(int startX, int startY, int dx, int dy) { 129 | mScroller.startScroll(startX, startY, dx, dy); 130 | } 131 | 132 | /** 133 | * Start scrolling by providing a starting point and the distance to travel. 134 | * 135 | * @param startX Starting horizontal scroll offset in pixels. Positive 136 | * numbers will scroll the content to the left. 137 | * @param startY Starting vertical scroll offset in pixels. Positive numbers 138 | * will scroll the content up. 139 | * @param dx Horizontal distance to travel. Positive numbers will scroll the 140 | * content to the left. 141 | * @param dy Vertical distance to travel. Positive numbers will scroll the 142 | * content up. 143 | * @param duration Duration of the scroll in milliseconds. 144 | */ 145 | public void startScroll(int startX, int startY, int dx, int dy, int duration) { 146 | mScroller.startScroll(startX, startY, dx, dy, duration); 147 | } 148 | 149 | /** 150 | * Start scrolling based on a fling gesture. The distance travelled will 151 | * depend on the initial velocity of the fling. 152 | * 153 | * @param startX Starting point of the scroll (X) 154 | * @param startY Starting point of the scroll (Y) 155 | * @param velocityX Initial velocity of the fling (X) measured in pixels per 156 | * second. 157 | * @param velocityY Initial velocity of the fling (Y) measured in pixels per 158 | * second 159 | * @param minX Minimum X value. The scroller will not scroll past this 160 | * point. 161 | * @param maxX Maximum X value. The scroller will not scroll past this 162 | * point. 163 | * @param minY Minimum Y value. The scroller will not scroll past this 164 | * point. 165 | * @param maxY Maximum Y value. The scroller will not scroll past this 166 | * point. 167 | */ 168 | public void fling(int startX, int startY, int velocityX, int velocityY, 169 | int minX, int maxX, int minY, int maxY) { 170 | mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); 171 | } 172 | 173 | /** 174 | * Stops the animation. Contrary to {@link #forceFinished(boolean)}, 175 | * aborting the animating cause the scroller to move to the final x and y 176 | * position 177 | */ 178 | public void abortAnimation() { 179 | mScroller.abortAnimation(); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /overscrolllib/src/main/java/com/wcy/overscroll/OverScrollLayout.java: -------------------------------------------------------------------------------- 1 | package com.wcy.overscroll; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.ViewCompat; 5 | import android.support.v4.view.ViewPager; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.support.v7.widget.StaggeredGridLayoutManager; 9 | import android.util.AttributeSet; 10 | import android.util.Log; 11 | import android.view.GestureDetector; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | import android.view.ViewConfiguration; 15 | import android.view.animation.AccelerateInterpolator; 16 | import android.view.animation.Animation; 17 | import android.view.animation.OvershootInterpolator; 18 | import android.webkit.WebView; 19 | import android.widget.AbsListView; 20 | import android.widget.HorizontalScrollView; 21 | import android.widget.LinearLayout; 22 | import android.widget.OverScroller; 23 | import android.widget.RelativeLayout; 24 | import android.widget.ScrollView; 25 | import android.widget.Scroller; 26 | 27 | /** 28 | * Created by changyou on 2016/4/7. 29 | */ 30 | public class OverScrollLayout extends RelativeLayout { 31 | private static final String TAG = "OverScrollLayout"; 32 | private ViewConfiguration configuration; 33 | private View child; 34 | private float downY; 35 | private float oldY; 36 | private int dealtY; 37 | private Scroller mScroller; 38 | 39 | private float downX; 40 | private float oldX; 41 | private int dealtX; 42 | private boolean isVerticalMove; 43 | private boolean isHorizontallyMove; 44 | 45 | private boolean isOverScrollTop; 46 | private boolean isOverScrollBottom; 47 | private boolean isOverScrollLeft; 48 | private boolean isOverScrollRight; 49 | 50 | private boolean checkScrollDirectionFinish; 51 | private boolean canOverScrollHorizontally; 52 | private boolean canOverScrollVertical; 53 | private float baseOverScrollLength; 54 | 55 | private boolean topOverScrollEnable = true; 56 | private boolean bottomOverScrollEnable = true; 57 | private boolean leftOverScrollEnable = true; 58 | private boolean rightOverScrollEnable = true; 59 | private OnOverScrollListener onOverScrollListener; 60 | private OverScrollCheckListener checkListener; 61 | 62 | public static int SCROLL_VERTICAL = LinearLayout.VERTICAL; 63 | public static int SCROLL_HORIZONTAL = LinearLayout.HORIZONTAL; 64 | 65 | private float fraction = 0.5f; 66 | private boolean finishOverScroll; 67 | private boolean abortScroller; 68 | private boolean shouldSetScrollerStart; 69 | private boolean disallowIntercept; 70 | 71 | private GestureDetector detector; 72 | 73 | private FlingRunnable flingRunnable; 74 | private OverScroller flingScroller; 75 | private OverScrollRunnable overScrollRunnable; 76 | 77 | public OverScrollLayout(Context context) { 78 | super(context); 79 | init(); 80 | } 81 | 82 | public OverScrollLayout(Context context, AttributeSet attrs) { 83 | super(context, attrs); 84 | init(); 85 | } 86 | 87 | public OverScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) { 88 | super(context, attrs, defStyleAttr); 89 | init(); 90 | } 91 | 92 | @SuppressWarnings("NewApi") 93 | public OverScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 94 | super(context, attrs, defStyleAttr, defStyleRes); 95 | init(); 96 | } 97 | 98 | private void init() { 99 | configuration = ViewConfiguration.get(getContext()); 100 | mScroller = new Scroller(getContext(), new OvershootInterpolator(0.75f)); 101 | flingRunnable = new FlingRunnable(); 102 | overScrollRunnable = new OverScrollRunnable(); 103 | flingScroller = new OverScroller(getContext()); 104 | detector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { 105 | @Override 106 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 107 | if (isOverScrollTop || isOverScrollBottom || isOverScrollLeft || isOverScrollRight) { 108 | return false; 109 | } 110 | // 111 | flingRunnable.start(velocityX, velocityY); 112 | return false; 113 | } 114 | }); 115 | } 116 | 117 | @Override 118 | protected void onFinishInflate() { 119 | int childCount = getChildCount(); 120 | if (childCount > 1) { 121 | throw new IllegalStateException("OverScrollLayout only can host 1 element"); 122 | } else if (childCount == 1) { 123 | child = getChildAt(0); 124 | child.setOverScrollMode(OVER_SCROLL_NEVER); 125 | } 126 | super.onFinishInflate(); 127 | } 128 | 129 | public void setDisallowInterceptTouchEvent(boolean disallowIntercept) { 130 | this.disallowIntercept = disallowIntercept; 131 | } 132 | 133 | public boolean isTopOverScrollEnable() { 134 | return topOverScrollEnable; 135 | } 136 | 137 | /** 138 | * @param topOverScrollEnable true can over scroll top false otherwise 139 | */ 140 | public void setTopOverScrollEnable(boolean topOverScrollEnable) { 141 | this.topOverScrollEnable = topOverScrollEnable; 142 | } 143 | 144 | public boolean isBottomOverScrollEnable() { 145 | return bottomOverScrollEnable; 146 | } 147 | 148 | /** 149 | * @param bottomOverScrollEnable true can over scroll bottom false otherwise 150 | */ 151 | public void setBottomOverScrollEnable(boolean bottomOverScrollEnable) { 152 | this.bottomOverScrollEnable = bottomOverScrollEnable; 153 | } 154 | 155 | public boolean isLeftOverScrollEnable() { 156 | return leftOverScrollEnable; 157 | } 158 | 159 | /** 160 | * @param leftOverScrollEnable true can over scroll left false otherwise 161 | */ 162 | public void setLeftOverScrollEnable(boolean leftOverScrollEnable) { 163 | this.leftOverScrollEnable = leftOverScrollEnable; 164 | } 165 | 166 | public boolean isRightOverScrollEnable() { 167 | return rightOverScrollEnable; 168 | } 169 | 170 | /** 171 | * @param rightOverScrollEnable true can over scroll right false otherwise 172 | */ 173 | public void setRightOverScrollEnable(boolean rightOverScrollEnable) { 174 | this.rightOverScrollEnable = rightOverScrollEnable; 175 | } 176 | 177 | public OnOverScrollListener getOnOverScrollListener() { 178 | return onOverScrollListener; 179 | } 180 | 181 | /** 182 | * @param onOverScrollListener 183 | */ 184 | public void setOnOverScrollListener(OnOverScrollListener onOverScrollListener) { 185 | this.onOverScrollListener = onOverScrollListener; 186 | } 187 | 188 | public OverScrollCheckListener getOverScrollCheckListener() { 189 | return checkListener; 190 | } 191 | 192 | /** 193 | * @param checkListener for custom view check over scroll 194 | */ 195 | public void setOverScrollCheckListener(OverScrollCheckListener checkListener) { 196 | this.checkListener = checkListener; 197 | } 198 | 199 | public float getFraction() { 200 | return fraction; 201 | } 202 | 203 | /** 204 | * @param fraction the fraction for over scroll.it is num[0f,1f], 205 | */ 206 | public void setFraction(float fraction) { 207 | if (fraction < 0 || fraction > 1) { 208 | return; 209 | } 210 | this.fraction = fraction; 211 | } 212 | 213 | private void checkCanOverScrollDirection() { 214 | if (checkScrollDirectionFinish) { 215 | return; 216 | } 217 | if (checkListener != null) { 218 | int mOrientation = checkListener.getContentViewScrollDirection(); 219 | canOverScrollHorizontally = RecyclerView.HORIZONTAL == mOrientation; 220 | canOverScrollVertical = RecyclerView.VERTICAL == mOrientation; 221 | } else if (child instanceof AbsListView || child instanceof ScrollView || child instanceof WebView) { 222 | canOverScrollHorizontally = false; 223 | canOverScrollVertical = true; 224 | } else if (child instanceof RecyclerView) { 225 | RecyclerView recyclerView = (RecyclerView) child; 226 | RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 227 | int mOrientation = -1; 228 | if (layoutManager instanceof StaggeredGridLayoutManager) { 229 | mOrientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation(); 230 | } else if (layoutManager instanceof LinearLayoutManager) { 231 | LinearLayoutManager manager = (LinearLayoutManager) layoutManager; 232 | mOrientation = manager.getOrientation(); 233 | } 234 | canOverScrollHorizontally = RecyclerView.HORIZONTAL == mOrientation; 235 | canOverScrollVertical = RecyclerView.VERTICAL == mOrientation; 236 | } else if (child instanceof HorizontalScrollView) { 237 | canOverScrollHorizontally = true; 238 | canOverScrollVertical = false; 239 | } else if (child instanceof ViewPager) { 240 | //forbid ViewPager over scroll 241 | canOverScrollHorizontally = false; 242 | canOverScrollVertical = false; 243 | } else { 244 | canOverScrollHorizontally = false; 245 | canOverScrollVertical = true; 246 | } 247 | checkScrollDirectionFinish = true; 248 | if (canOverScrollVertical) { 249 | baseOverScrollLength = getHeight(); 250 | } else { 251 | baseOverScrollLength = getWidth(); 252 | } 253 | } 254 | 255 | @Override 256 | public void computeScroll() { 257 | if (mScroller.computeScrollOffset()) { 258 | int scrollerY = mScroller.getCurrY(); 259 | scrollTo(mScroller.getCurrX(), scrollerY); 260 | postInvalidate(); 261 | } else { 262 | if (abortScroller) { 263 | abortScroller = false; 264 | return; 265 | } 266 | if (finishOverScroll) { 267 | isOverScrollTop = false; 268 | isOverScrollBottom = false; 269 | isOverScrollLeft = false; 270 | isOverScrollRight = false; 271 | finishOverScroll = false; 272 | } 273 | } 274 | 275 | } 276 | 277 | protected void mSmoothScrollTo(int fx, int fy) { 278 | int dx = fx - mScroller.getFinalX(); 279 | int dy = fy - mScroller.getFinalY(); 280 | mSmoothScrollBy(dx, dy); 281 | } 282 | 283 | 284 | protected void mSmoothScrollBy(int dx, int dy) { 285 | mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy); 286 | invalidate(); 287 | } 288 | 289 | 290 | @Override 291 | public boolean dispatchTouchEvent(MotionEvent ev) { 292 | if (disallowIntercept) { 293 | return super.dispatchTouchEvent(ev); 294 | } 295 | 296 | detector.onTouchEvent(ev); 297 | 298 | int action = ev.getAction() & MotionEvent.ACTION_MASK; 299 | switch (action) { 300 | case MotionEvent.ACTION_POINTER_DOWN: 301 | oldY = 0; 302 | oldX = 0; 303 | break; 304 | case MotionEvent.ACTION_DOWN: 305 | flingRunnable.abort(); 306 | downY = ev.getY(); 307 | oldY = 0; 308 | dealtY = mScroller.getCurrY(); 309 | if (dealtY == 0) { 310 | isVerticalMove = false; 311 | } else { 312 | shouldSetScrollerStart = true; 313 | abortScroller = true; 314 | mScroller.abortAnimation(); 315 | } 316 | 317 | downX = ev.getX(); 318 | oldX = 0; 319 | dealtX = mScroller.getCurrX(); 320 | if (dealtX == 0) { 321 | isHorizontallyMove = false; 322 | } else { 323 | shouldSetScrollerStart = true; 324 | abortScroller = true; 325 | mScroller.abortAnimation(); 326 | } 327 | if (isOverScrollTop || isOverScrollBottom || isOverScrollLeft || isOverScrollRight) { 328 | return true; 329 | } 330 | checkCanOverScrollDirection(); 331 | break; 332 | 333 | case MotionEvent.ACTION_MOVE: 334 | 335 | if (!canOverScroll()) { 336 | return super.dispatchTouchEvent(ev); 337 | } 338 | 339 | if (canOverScrollVertical) { 340 | if (isOverScrollTop || isOverScrollBottom) { 341 | if (onOverScrollListener != null) { 342 | if (isOverScrollTop) { 343 | onOverScrollListener.onTopOverScroll(); 344 | } 345 | if (isOverScrollBottom) { 346 | onOverScrollListener.onBottomOverScroll(); 347 | } 348 | } 349 | if (shouldSetScrollerStart) { 350 | shouldSetScrollerStart = false; 351 | mScroller.startScroll(dealtX, dealtY, 0, 0); 352 | } 353 | if (oldY == 0) { 354 | oldY = ev.getY(); 355 | return true; 356 | } 357 | dealtY += getDealt(oldY - ev.getY(), dealtY); 358 | oldY = ev.getY(); 359 | if (isOverScrollTop && dealtY > 0) { 360 | dealtY = 0; 361 | } 362 | if (isOverScrollBottom && dealtY < 0) { 363 | dealtY = 0; 364 | } 365 | overScroll(dealtX, dealtY); 366 | if ((isOverScrollTop && dealtY == 0 && !isOverScrollBottom) || 367 | (isOverScrollBottom && dealtY == 0 && !isOverScrollTop)) { 368 | oldY = 0; 369 | isOverScrollTop = false; 370 | isOverScrollBottom = false; 371 | if (!isChildCanScrollVertical()) { 372 | return true; 373 | } 374 | return super.dispatchTouchEvent(resetVertical(ev)); 375 | } 376 | return true; 377 | } else { 378 | checkMoveDirection(ev.getX(), ev.getY()); 379 | if (oldY == 0) { 380 | oldY = ev.getY(); 381 | return true; 382 | } 383 | boolean tempOverScrollTop = isTopOverScroll(ev.getY()); 384 | if (!isOverScrollTop && tempOverScrollTop) { 385 | oldY = ev.getY(); 386 | isOverScrollTop = tempOverScrollTop; 387 | ev.setAction(MotionEvent.ACTION_CANCEL); 388 | super.dispatchTouchEvent(ev); 389 | return true; 390 | } 391 | isOverScrollTop = tempOverScrollTop; 392 | boolean tempOverScrollBottom = isBottomOverScroll(ev.getY()); 393 | if (!isOverScrollBottom && tempOverScrollBottom) { 394 | oldY = ev.getY(); 395 | isOverScrollBottom = tempOverScrollBottom; 396 | ev.setAction(MotionEvent.ACTION_CANCEL); 397 | super.dispatchTouchEvent(ev); 398 | return true; 399 | } 400 | isOverScrollBottom = tempOverScrollBottom; 401 | oldY = ev.getY(); 402 | } 403 | } else if (canOverScrollHorizontally) { 404 | if (isOverScrollLeft || isOverScrollRight) { 405 | if (onOverScrollListener != null) { 406 | if (isOverScrollLeft) { 407 | onOverScrollListener.onLeftOverScroll(); 408 | } 409 | if (isOverScrollRight) { 410 | onOverScrollListener.onRightOverScroll(); 411 | } 412 | } 413 | if (shouldSetScrollerStart) { 414 | shouldSetScrollerStart = false; 415 | mScroller.startScroll(dealtX, dealtY, 0, 0); 416 | } 417 | if (oldX == 0) { 418 | oldX = ev.getX(); 419 | return true; 420 | } 421 | dealtX += getDealt(oldX - ev.getX(), dealtX); 422 | oldX = ev.getX(); 423 | if (isOverScrollLeft && dealtX > 0) { 424 | dealtX = 0; 425 | } 426 | if (isOverScrollRight && dealtX < 0) { 427 | dealtX = 0; 428 | } 429 | overScroll(dealtX, dealtY); 430 | if ((isOverScrollLeft && dealtX == 0 && !isOverScrollRight) || 431 | (isOverScrollRight && dealtX == 0 && !isOverScrollLeft)) { 432 | oldX = 0; 433 | isOverScrollRight = false; 434 | isOverScrollLeft = false; 435 | if (!isChildCanScrollHorizontally()) { 436 | return true; 437 | } 438 | return super.dispatchTouchEvent(resetHorizontally(ev)); 439 | } 440 | return true; 441 | } else { 442 | checkMoveDirection(ev.getX(), ev.getY()); 443 | if (oldX == 0) { 444 | oldX = ev.getX(); 445 | return true; 446 | } 447 | boolean tempOverScrollLeft = isLeftOverScroll(ev.getX()); 448 | if (!isOverScrollLeft && tempOverScrollLeft) { 449 | oldX = ev.getX(); 450 | isOverScrollLeft = tempOverScrollLeft; 451 | ev.setAction(MotionEvent.ACTION_CANCEL); 452 | super.dispatchTouchEvent(ev); 453 | return true; 454 | } 455 | isOverScrollLeft = tempOverScrollLeft; 456 | boolean tempOverScrollRight = isRightOverScroll(ev.getX()); 457 | if (!isOverScrollRight && tempOverScrollRight) { 458 | oldX = ev.getX(); 459 | isOverScrollRight = tempOverScrollRight; 460 | ev.setAction(MotionEvent.ACTION_CANCEL); 461 | super.dispatchTouchEvent(ev); 462 | return true; 463 | } 464 | isOverScrollRight = tempOverScrollRight; 465 | oldX = ev.getX(); 466 | } 467 | } 468 | break; 469 | case MotionEvent.ACTION_POINTER_UP: 470 | oldY = 0; 471 | oldX = 0; 472 | break; 473 | case MotionEvent.ACTION_CANCEL: 474 | case MotionEvent.ACTION_UP: 475 | finishOverScroll = true; 476 | mSmoothScrollTo(0, 0); 477 | break; 478 | } 479 | return super.dispatchTouchEvent(ev); 480 | 481 | } 482 | 483 | private float getDealt(float dealt, float distance) { 484 | if (dealt * distance < 0) 485 | return dealt; 486 | //x 为0的时候 y 一直为0, 所以当x==0的时候,给一个0.1的最小值 487 | float x = (float) Math.min(Math.max(Math.abs(distance), 0.1) / Math.abs(baseOverScrollLength), 1); 488 | float y = Math.min(new AccelerateInterpolator(0.15f).getInterpolation(x), 1); 489 | return dealt * (1 - y); 490 | } 491 | 492 | private MotionEvent resetVertical(MotionEvent event) { 493 | oldY = 0; 494 | dealtY = 0; 495 | event.setAction(MotionEvent.ACTION_DOWN); 496 | super.dispatchTouchEvent(event); 497 | event.setAction(MotionEvent.ACTION_MOVE); 498 | return event; 499 | } 500 | 501 | private MotionEvent resetHorizontally(MotionEvent event) { 502 | oldX = 0; 503 | dealtX = 0; 504 | event.setAction(MotionEvent.ACTION_DOWN); 505 | super.dispatchTouchEvent(event); 506 | event.setAction(MotionEvent.ACTION_MOVE); 507 | return event; 508 | } 509 | 510 | private boolean canOverScroll() { 511 | return child != null; 512 | } 513 | 514 | 515 | private void overScroll(int dealtX, int dealtY) { 516 | mSmoothScrollTo(dealtX, dealtY); 517 | } 518 | 519 | private boolean isTopOverScroll(float currentY) { 520 | if (isOverScrollTop) { 521 | return true; 522 | } 523 | if (!topOverScrollEnable || !isVerticalMove) { 524 | return false; 525 | } 526 | float dealtY = oldY - currentY; 527 | return dealtY < 0 && !canChildScrollUp(); 528 | } 529 | 530 | private boolean isBottomOverScroll(float currentY) { 531 | if (isOverScrollBottom) { 532 | return true; 533 | } 534 | if (!bottomOverScrollEnable || !isVerticalMove) { 535 | return false; 536 | } 537 | float dealtY = oldY - currentY; 538 | return dealtY > 0 && !canChildScrollDown(); 539 | } 540 | 541 | private boolean isLeftOverScroll(float currentX) { 542 | if (isOverScrollLeft) { 543 | return true; 544 | } 545 | if (!leftOverScrollEnable || !isHorizontallyMove) { 546 | return false; 547 | } 548 | float dealtX = oldX - currentX; 549 | return dealtX < 0 && !canChildScrollLeft(); 550 | } 551 | 552 | private boolean isRightOverScroll(float currentX) { 553 | if (!rightOverScrollEnable || !isHorizontallyMove) { 554 | return false; 555 | } 556 | float dealtX = oldX - currentX; 557 | return dealtX > 0 && !canChildScrollRight(); 558 | } 559 | 560 | private boolean isChildCanScrollVertical() { 561 | return canChildScrollDown() || canChildScrollUp(); 562 | } 563 | 564 | private boolean isChildCanScrollHorizontally() { 565 | return canChildScrollLeft() || canChildScrollRight(); 566 | } 567 | 568 | private void checkMoveDirection(float currentX, float currentY) { 569 | if (isVerticalMove || isHorizontallyMove) { 570 | return; 571 | } 572 | if (canOverScrollVertical) { 573 | float dealtY = currentY - downY; 574 | isVerticalMove = Math.abs(dealtY) >= configuration.getScaledTouchSlop(); 575 | } else if (canOverScrollHorizontally) { 576 | float dealtX = currentX - downX; 577 | isHorizontallyMove = Math.abs(dealtX) >= configuration.getScaledTouchSlop(); 578 | } 579 | } 580 | 581 | @Override 582 | public boolean onTouchEvent(MotionEvent event) { 583 | return true; 584 | } 585 | 586 | /** 587 | * 是否能下拉 588 | * 589 | * @return 590 | */ 591 | private boolean canChildScrollUp() { 592 | if (checkListener != null) { 593 | return checkListener.canScrollUp(); 594 | } 595 | if (android.os.Build.VERSION.SDK_INT < 14) { 596 | if (child instanceof AbsListView) { 597 | final AbsListView absListView = (AbsListView) child; 598 | return absListView.getChildCount() > 0 599 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 600 | .getTop() < absListView.getPaddingTop()); 601 | } 602 | } 603 | return ViewCompat.canScrollVertically(child, -1); 604 | 605 | } 606 | 607 | 608 | /** 609 | * 是否能上拉 610 | * 611 | * @return 612 | */ 613 | private boolean canChildScrollDown() { 614 | if (checkListener != null) { 615 | return checkListener.canScrollDown(); 616 | } 617 | if (android.os.Build.VERSION.SDK_INT < 14) { 618 | if (child instanceof AbsListView) { 619 | final AbsListView absListView = (AbsListView) child; 620 | return absListView.getChildCount() > 0 621 | && (absListView.getLastVisiblePosition() < absListView.getChildCount() - 1 622 | || absListView.getChildAt(absListView.getChildCount() - 1).getBottom() > absListView.getHeight() - absListView.getPaddingBottom()); 623 | } 624 | } 625 | return ViewCompat.canScrollVertically(child, 1); 626 | 627 | } 628 | 629 | /** 630 | * 是否能左拉 631 | * 632 | * @return 633 | */ 634 | private boolean canChildScrollLeft() { 635 | if (checkListener != null) { 636 | return checkListener.canScrollLeft(); 637 | } 638 | return ViewCompat.canScrollHorizontally(child, -1); 639 | } 640 | 641 | /** 642 | * 是否能右拉 643 | * 644 | * @return 645 | */ 646 | private boolean canChildScrollRight() { 647 | if (checkListener != null) { 648 | return checkListener.canScrollRight(); 649 | } 650 | return ViewCompat.canScrollHorizontally(child, 1); 651 | } 652 | 653 | private void startOverScrollAim(float currVelocity) { 654 | float speed = currVelocity / configuration.getScaledMaximumFlingVelocity(); 655 | if (canOverScrollVertical) { 656 | if (!canChildScrollUp()) { 657 | overScrollRunnable.start(0, -speed); 658 | } else { 659 | overScrollRunnable.start(0, speed); 660 | } 661 | } else { 662 | if (canChildScrollRight()) { 663 | overScrollRunnable.start(-speed, 0); 664 | } else { 665 | overScrollRunnable.start(speed, 0); 666 | } 667 | } 668 | } 669 | 670 | private class OverScrollRunnable implements Runnable { 671 | 672 | private static final long DELAY_TIME = 20; 673 | private long duration = 160; 674 | private float speedX, speedY; 675 | private long timePass; 676 | private long startTime; 677 | private int distanceX, distanceY; 678 | 679 | public void start(float speedX, float speedY) { 680 | this.speedX = speedX; 681 | this.speedY = speedY; 682 | startTime = System.currentTimeMillis(); 683 | run(); 684 | } 685 | 686 | @Override 687 | public void run() { 688 | timePass = System.currentTimeMillis() - startTime; 689 | if (timePass < duration) { 690 | distanceY = (int) (DELAY_TIME * speedY); 691 | distanceX = (int) (DELAY_TIME * speedX); 692 | mSmoothScrollBy(distanceX, distanceY); 693 | postDelayed(this, DELAY_TIME); 694 | } else if (timePass > duration) { 695 | mSmoothScrollTo(0, 0); 696 | } 697 | } 698 | } 699 | 700 | private class FlingRunnable implements Runnable { 701 | private static final long DELAY_TIME = 40; 702 | private boolean abort; 703 | private int mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); 704 | 705 | public void start(float velocityX, float velocityY) { 706 | abort = false; 707 | float velocity = canOverScrollVertical ? velocityY : velocityX; 708 | flingScroller.fling(0, 0, 0, (int) velocity, 0, 0, 709 | Integer.MIN_VALUE, Integer.MAX_VALUE); 710 | postDelayed(this, 40); 711 | } 712 | 713 | @Override 714 | public void run() { 715 | if (!abort && flingScroller.computeScrollOffset()) { 716 | boolean scrollEnd = false; 717 | if (canOverScrollVertical) { 718 | scrollEnd = !canChildScrollDown() || !canChildScrollUp(); 719 | } else { 720 | scrollEnd = !canChildScrollLeft() || !canChildScrollRight(); 721 | } 722 | 723 | float currVelocity = flingScroller.getCurrVelocity(); 724 | if (scrollEnd) { 725 | if (currVelocity > mMinimumFlingVelocity) { 726 | startOverScrollAim(currVelocity); 727 | } 728 | } else { 729 | if (currVelocity > mMinimumFlingVelocity) { 730 | postDelayed(this, DELAY_TIME); 731 | } 732 | } 733 | 734 | } 735 | 736 | 737 | } 738 | 739 | public void abort() { 740 | abort = true; 741 | } 742 | } 743 | } 744 | --------------------------------------------------------------------------------