25 | * Author:donkingliang QQ:1043214265 26 | * Dat:2018/05/24 27 | */ 28 | public class RefreshLayout extends ViewGroup { 29 | 30 | protected Context mContext; 31 | 32 | protected int mTouchSlop; 33 | 34 | /* 35 | 触发下拉刷新的最小高度。 36 | 一般来说,触发下拉刷新的高度就是头部View的高度 37 | */ 38 | private int mHeaderTriggerMinHeight = 100; 39 | 40 | /* 41 | 触发下拉刷新的最大高度。 42 | 一般来说,触发下拉刷新的高度就是头部View的高度 43 | */ 44 | private int mHeaderTriggerMaxHeight = 400; 45 | 46 | /* 47 | 触发上拉加载的最小高度。 48 | 一般来说,触发上拉加载的高度就是尾部View的高度 49 | */ 50 | private int mFooterTriggerMinHeight = 100; 51 | 52 | /* 53 | 触发上拉加载的最大高度。 54 | 一般来说,触发上拉加载的高度就是尾部View的高度 55 | */ 56 | private int mFooterTriggerMaxHeight = 400; 57 | 58 | //头部容器 59 | private LinearLayout mHeaderLayout; 60 | 61 | //头部View 62 | private View mHeaderView; 63 | 64 | //尾部容器 65 | private LinearLayout mFooterLayout; 66 | 67 | //尾部View 68 | private View mFooterView; 69 | 70 | //标记 无状态(既不是上拉 也 不是下拉) 71 | private final int STATE_NOT = -1; 72 | 73 | //标记 上拉状态 74 | private final int STATE_UP = 1; 75 | 76 | //标记 下拉状态 77 | private final int STATE_DOWN = 2; 78 | 79 | //当前状态 80 | private int mCurrentState = STATE_NOT; 81 | 82 | //是否处于正在下拉刷新状态 83 | private boolean mIsRefreshing = false; 84 | 85 | //是否处于正在上拉加载状态 86 | private boolean mIsLoadingMore = false; 87 | 88 | //是否启用下拉功能(默认开启) 89 | private boolean mIsRefresh = true; 90 | 91 | /* 92 | 是否启用上拉功能(默认不开启) 93 | 如果设置了上拉加载监听器OnLoadMoreListener,就会自动开启。 94 | */ 95 | private boolean mIsLoadMore = false; 96 | 97 | //上拉、下拉的阻尼 设置上下拉时的拖动阻力效果 98 | private int mDamp = 4; 99 | 100 | //头部状态监听器 101 | private OnHeaderStateListener mOnHeaderStateListener; 102 | 103 | //尾部状态监听器 104 | private OnFooterStateListener mOnFooterStateListener; 105 | 106 | //下拉刷新监听器 107 | private OnRefreshListener mOnRefreshListener; 108 | 109 | //上拉加载监听器 110 | private OnLoadMoreListener mOnLoadMoreListener; 111 | 112 | //是否还有更多数据 113 | private boolean mHasMore = true; 114 | 115 | //是否显示空布局 116 | private boolean mIsEmpty = false; 117 | 118 | //滑动到底部,自动触发加载更多 119 | private boolean mAutoLoadMore = true; 120 | 121 | //是否拦截触摸事件, 122 | private boolean mInterceptTouchEvent = false; 123 | 124 | //---------------- 用于监听手指松开时,屏幕的滑动状态 -------------------// 125 | //手指松开时,不一定是滑动停止,也有可能是Fling,所以需要监听屏幕滑动的情况。 126 | // 每隔50毫秒获取一下页面的滑动距离,如果跟上次没有变化,表示滑动停止。 127 | // 之所以用延时获取滑动距离的方式获取滑动状态,是因为在sdk 23前,无法给所有的View设置OnScrollChangeListener。 128 | private final int SCROLL_DELAY = 50; 129 | private Handler mScrollHandler = new Handler(); 130 | private Runnable mScrollChangeListener = new Runnable() { 131 | @Override 132 | public void run() { 133 | if (listenScrollChange()) { 134 | mScrollHandler.postDelayed(mScrollChangeListener, SCROLL_DELAY); 135 | } else { 136 | mFlingOrientation = ORIENTATION_FLING_NONE; 137 | } 138 | } 139 | }; 140 | 141 | private int oldOffsetY; 142 | private int mFlingOrientation; 143 | private static final int ORIENTATION_FLING_NONE = 0; 144 | private static final int ORIENTATION_FLING_UP = 1; 145 | private static final int ORIENTATION_FLING_DOWN = 2; 146 | 147 | //手指触摸屏幕时的触摸点 148 | int mTouchX = 0; 149 | int mTouchY = 0; 150 | 151 | public RefreshLayout(Context context) { 152 | this(context, null); 153 | } 154 | 155 | public RefreshLayout(Context context, AttributeSet attrs) { 156 | this(context, attrs, 0); 157 | } 158 | 159 | public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { 160 | super(context, attrs, defStyleAttr); 161 | mContext = context; 162 | init(); 163 | } 164 | 165 | private void init() { 166 | setClipToPadding(false); 167 | initHeaderLayout(); 168 | initFooterLayout(); 169 | ViewConfiguration configuration = ViewConfiguration.get(mContext); 170 | mTouchSlop = configuration.getScaledTouchSlop(); 171 | } 172 | 173 | /** 174 | * 初始化头部 175 | */ 176 | private void initHeaderLayout() { 177 | mHeaderLayout = new LinearLayout(mContext); 178 | LayoutParams lp = new LayoutParams( 179 | LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); 180 | mHeaderLayout.setGravity(Gravity.CENTER_HORIZONTAL); 181 | mHeaderLayout.setLayoutParams(lp); 182 | addView(mHeaderLayout); 183 | } 184 | 185 | /** 186 | * 设置头部View 187 | * 188 | * @param headerView 头部View。这个View必须实现{@link OnHeaderStateListener}接口。 189 | */ 190 | public void setHeaderView(@NonNull View headerView) { 191 | if (headerView instanceof OnHeaderStateListener) { 192 | mHeaderView = headerView; 193 | mHeaderLayout.removeAllViews(); 194 | mHeaderLayout.addView(mHeaderView); 195 | mOnHeaderStateListener = (OnHeaderStateListener) headerView; 196 | } else { 197 | // headerView必须实现OnHeaderStateListener接口, 198 | // 并通过OnHeaderStateListener的回调来更新headerView的状态。 199 | throw new IllegalArgumentException("headerView must implement the OnHeaderStateListener"); 200 | } 201 | } 202 | 203 | /** 204 | * 初始化尾部 205 | */ 206 | private void initFooterLayout() { 207 | mFooterLayout = new LinearLayout(mContext); 208 | LayoutParams lp = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); 209 | mFooterLayout.setGravity(Gravity.CENTER_HORIZONTAL); 210 | mFooterLayout.setLayoutParams(lp); 211 | addView(mFooterLayout); 212 | } 213 | 214 | /** 215 | * 设置尾部View 216 | * 217 | * @param footerView 尾部View。这个View必须实现{@link OnFooterStateListener}接口 218 | */ 219 | public void setFooterView(@NonNull View footerView) { 220 | if (footerView instanceof OnFooterStateListener) { 221 | mFooterView = footerView; 222 | mFooterLayout.removeAllViews(); 223 | mFooterLayout.addView(mFooterView); 224 | mOnFooterStateListener = (OnFooterStateListener) footerView; 225 | } else { 226 | // footerView必须实现OnFooterStateListener接口, 227 | // 并通过OnFooterStateListener的回调来更新footerView的状态。 228 | throw new IllegalArgumentException("footerView must implement the OnFooterStateListener"); 229 | } 230 | } 231 | 232 | @Override 233 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 234 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 235 | //测量头部高度 236 | View headerView = getChildAt(0); 237 | measureChild(headerView, widthMeasureSpec, heightMeasureSpec); 238 | 239 | //测量尾部高度 240 | View footerView = getChildAt(1); 241 | measureChild(footerView, widthMeasureSpec, heightMeasureSpec); 242 | 243 | //测量内容容器宽高 244 | int count = getChildCount(); 245 | int contentHeight = 0; 246 | int contentWidth = 0; 247 | if (mIsEmpty) { 248 | //空布局容器 249 | if (count > 3) { 250 | View emptyView = getChildAt(3); 251 | measureChildWithMargins(emptyView, widthMeasureSpec, 0, heightMeasureSpec, 0); 252 | MarginLayoutParams emptyLp = (MarginLayoutParams) emptyView.getLayoutParams(); 253 | contentHeight = emptyView.getMeasuredHeight() + emptyLp.topMargin + emptyLp.bottomMargin; 254 | contentWidth = emptyView.getMeasuredWidth() + emptyLp.leftMargin + emptyLp.rightMargin; 255 | } 256 | } else { 257 | //内容布局容器 258 | if (count > 2) { 259 | View content = getChildAt(2); 260 | measureChildWithMargins(content, widthMeasureSpec, 0, heightMeasureSpec, 0); 261 | MarginLayoutParams contentLp = (MarginLayoutParams) content.getLayoutParams(); 262 | contentHeight = content.getMeasuredHeight() + contentLp.topMargin + contentLp.bottomMargin; 263 | contentWidth = content.getMeasuredWidth() + contentLp.leftMargin + contentLp.rightMargin; 264 | } 265 | } 266 | 267 | setMeasuredDimension(measureWidth(widthMeasureSpec, contentWidth), 268 | measureHeight(heightMeasureSpec, contentHeight)); 269 | } 270 | 271 | @Override 272 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 273 | //布局头部 274 | View headerView = getChildAt(0); 275 | headerView.layout(getPaddingLeft(), -headerView.getMeasuredHeight(), 276 | getPaddingLeft() + headerView.getMeasuredWidth(), 0); 277 | 278 | //布局尾部 279 | View footerView = getChildAt(1); 280 | footerView.layout(getPaddingLeft(), getMeasuredHeight(), 281 | getPaddingLeft() + footerView.getMeasuredWidth(), 282 | getMeasuredHeight() + footerView.getMeasuredHeight()); 283 | 284 | int count = getChildCount(); 285 | if (mIsEmpty) { 286 | //空布局容器 287 | if (count > 3) { 288 | View emptyView = getChildAt(3); 289 | MarginLayoutParams emptyLp = (MarginLayoutParams) emptyView.getLayoutParams(); 290 | emptyView.layout(getPaddingLeft() + emptyLp.leftMargin, 291 | getPaddingTop() + emptyLp.topMargin, 292 | getPaddingLeft() + emptyLp.leftMargin + emptyView.getMeasuredWidth(), 293 | getPaddingTop() + emptyLp.topMargin + emptyView.getMeasuredHeight()); 294 | } 295 | } else { 296 | //内容布局容器 297 | if (count > 2) { 298 | View content = getChildAt(2); 299 | MarginLayoutParams contentLp = (MarginLayoutParams) content.getLayoutParams(); 300 | content.layout(getPaddingLeft() + contentLp.leftMargin, 301 | getPaddingTop() + contentLp.topMargin, 302 | getPaddingLeft() + contentLp.leftMargin + content.getMeasuredWidth(), 303 | getPaddingTop() + contentLp.topMargin + content.getMeasuredHeight()); 304 | } 305 | } 306 | } 307 | 308 | private int measureWidth(int measureSpec, int contentWidth) { 309 | int result = 0; 310 | int specMode = MeasureSpec.getMode(measureSpec); 311 | int specSize = MeasureSpec.getSize(measureSpec); 312 | 313 | if (specMode == MeasureSpec.EXACTLY) { 314 | result = specSize; 315 | } else { 316 | result = contentWidth + getPaddingLeft() + getPaddingRight(); 317 | if (specMode == MeasureSpec.AT_MOST) { 318 | result = Math.min(result, specSize); 319 | } 320 | } 321 | 322 | return result; 323 | } 324 | 325 | private int measureHeight(int measureSpec, int contentHeight) { 326 | int result = 0; 327 | int specMode = MeasureSpec.getMode(measureSpec); 328 | int specSize = MeasureSpec.getSize(measureSpec); 329 | 330 | if (specMode == MeasureSpec.EXACTLY) { 331 | result = specSize; 332 | } else { 333 | result = contentHeight + getPaddingTop() + getPaddingBottom(); 334 | if (specMode == MeasureSpec.AT_MOST) { 335 | result = Math.min(result, specSize); 336 | } 337 | } 338 | 339 | return result; 340 | } 341 | 342 | @Override 343 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 344 | return new MarginLayoutParams(getContext(), attrs); 345 | } 346 | 347 | int oldY; 348 | 349 | @Override 350 | public boolean dispatchTouchEvent(MotionEvent ev) { 351 | 352 | switch (ev.getAction()) { 353 | case MotionEvent.ACTION_DOWN: 354 | mCurrentState = STATE_NOT; 355 | mTouchX = (int) ev.getX(); 356 | mTouchY = (int) ev.getY(); 357 | oldY = (int) ev.getY(); 358 | mScrollHandler.removeCallbacksAndMessages(null); 359 | break; 360 | case MotionEvent.ACTION_MOVE: 361 | int newY = (int) ev.getY(); 362 | if (isNestedScroll()) { 363 | if ((canPullDown() && newY > oldY) 364 | || (canPullUp() && newY < oldY)) { 365 | nestedScroll(oldY - newY); 366 | } 367 | 368 | if ((getScrollY() > 0 && newY > oldY) 369 | || (getScrollY() < 0 && newY < oldY)) { 370 | nestedPreScroll(oldY - newY); 371 | } 372 | } 373 | 374 | if (newY > oldY) { 375 | mFlingOrientation = ORIENTATION_FLING_UP; 376 | } else if (newY < oldY) { 377 | mFlingOrientation = ORIENTATION_FLING_DOWN; 378 | } 379 | 380 | oldY = newY; 381 | 382 | break; 383 | case MotionEvent.ACTION_UP: 384 | 385 | int y = (int) ev.getY(); 386 | int x = (int) ev.getX(); 387 | //是否是点击事件,如果按下和松开的坐标没有改变,就认为是点击事件 388 | boolean isClick = Math.abs(mTouchX - x) < mTouchSlop && Math.abs(mTouchY - y) < mTouchSlop; 389 | 390 | if (!mInterceptTouchEvent && !isClick && !mIsEmpty) { 391 | //监听手指松开时,屏幕的滑动状态 392 | //手指松开时,不一定是滑动停止,也有可能是Fling,所以需要监听屏幕滑动的情况。 393 | initScrollListen(); 394 | mScrollHandler.postDelayed(mScrollChangeListener, SCROLL_DELAY); 395 | } 396 | 397 | mTouchX = 0; 398 | mTouchY = 0; 399 | break; 400 | } 401 | return super.dispatchTouchEvent(ev); 402 | } 403 | 404 | @Override 405 | public boolean onInterceptTouchEvent(MotionEvent ev) { 406 | 407 | int y = (int) ev.getY(); 408 | 409 | switch (ev.getAction()) { 410 | case MotionEvent.ACTION_DOWN: 411 | mInterceptTouchEvent = false; 412 | return false; 413 | case MotionEvent.ACTION_MOVE: 414 | if (isRefreshingOrLoading()) { 415 | return false; 416 | } 417 | 418 | if (pullRefresh() && y - mTouchY > mTouchSlop) { 419 | return true; 420 | } 421 | 422 | if (mHasMore && pullLoadMore() && mTouchY - y > mTouchSlop) { 423 | return true; 424 | } 425 | 426 | return false; 427 | case MotionEvent.ACTION_UP: 428 | return false; 429 | } 430 | 431 | return super.onInterceptTouchEvent(ev); 432 | } 433 | 434 | @Override 435 | public boolean onTouchEvent(MotionEvent event) { 436 | 437 | int y = (int) event.getY(); 438 | 439 | switch (event.getAction()) { 440 | case MotionEvent.ACTION_DOWN: 441 | return true; 442 | case MotionEvent.ACTION_MOVE: 443 | 444 | if (mCurrentState == STATE_NOT) { 445 | if (pullRefresh() && y - mTouchY > mTouchSlop) { 446 | mCurrentState = STATE_DOWN; 447 | mInterceptTouchEvent = true; 448 | } 449 | 450 | if (mHasMore && pullLoadMore() && mTouchY - y > mTouchSlop) { 451 | mCurrentState = STATE_UP; 452 | mInterceptTouchEvent = true; 453 | } 454 | } 455 | 456 | if (mTouchY > y) { 457 | if (mCurrentState == STATE_UP && !mIsEmpty) { 458 | scroll((mTouchY - y) / mDamp, true); 459 | } 460 | } else if (mCurrentState == STATE_DOWN || mIsEmpty) { 461 | scroll((mTouchY - y) / mDamp, true); 462 | } 463 | break; 464 | 465 | case MotionEvent.ACTION_UP: 466 | if (!mIsRefreshing && !mIsLoadingMore) { 467 | int scrollOffset = Math.abs(getScrollY()); 468 | if (mCurrentState == STATE_DOWN || mIsEmpty) { 469 | if (scrollOffset < getHeaderTriggerHeight()) { 470 | restore(true); 471 | } else { 472 | triggerRefresh(); 473 | } 474 | } else if (mCurrentState == STATE_UP) { 475 | if (scrollOffset < getFooterTriggerHeight()) { 476 | restore(true); 477 | } else { 478 | triggerLoadMore(); 479 | } 480 | } 481 | } 482 | break; 483 | 484 | default: 485 | break; 486 | } 487 | return super.onTouchEvent(event); 488 | } 489 | 490 | /** 491 | * 是否可下拉刷新。 492 | * 493 | * @return 启用了下拉刷新功能,并且可以下拉显示头部,返回true;否则返回false。 494 | */ 495 | protected boolean pullRefresh() { 496 | return mIsRefresh && canPullDown(); 497 | } 498 | 499 | /** 500 | * 是否可上拉加载更多。 501 | * 502 | * @return 启用了上拉加载更多功能,并且有更多数据,并且可以下拉显示头部,返回true,否则返回false。 503 | */ 504 | protected boolean pullLoadMore() { 505 | return mIsLoadMore && mHasMore && canPullUp(); 506 | } 507 | 508 | /** 509 | * 是否可下拉显示头部。 510 | * 511 | * @return 如果是空布局或者内容布局已经滑动到顶部,则返回true,否则返回false。 512 | */ 513 | protected boolean canPullDown() { 514 | 515 | if (mIsEmpty) { 516 | return true; 517 | } 518 | 519 | if (getChildCount() >= 3) { 520 | return computeVerticalScrollOffset(getChildAt(2)) <= 0; 521 | } 522 | return true; 523 | } 524 | 525 | /** 526 | * 是否可上拉显示尾部。 527 | * 528 | * @return 如果不是空布局,并且内容布局已经滑动到底部,则返回true,否则返回false。 529 | */ 530 | protected boolean canPullUp() { 531 | 532 | if (mIsEmpty) { 533 | return false; 534 | } 535 | 536 | if (getChildCount() >= 3) { 537 | View view = getChildAt(2); 538 | return computeVerticalScrollOffset(view) + computeVerticalScrollExtent(view) 539 | >= computeVerticalScrollRange(view); 540 | } 541 | return false; 542 | } 543 | 544 | /** 545 | * 是否正在刷新或者正在加载更多 546 | * 547 | * @return 548 | */ 549 | private boolean isRefreshingOrLoading() { 550 | return mIsRefreshing || mIsLoadingMore; 551 | } 552 | 553 | /** 554 | * 利用属性动画实现平滑滑动 555 | * 556 | * @param start 滑动的开始位置 557 | * @param end 滑动的结束位置 558 | * @param duration 滑动的持续时间 559 | * @param isListening 是否监听滑动变化 560 | * @param listener 561 | */ 562 | private void smoothScroll(int start, int end, int duration, final boolean isListening, 563 | Animator.AnimatorListener listener) { 564 | ValueAnimator animator = ValueAnimator.ofInt(start, end).setDuration(duration); 565 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 566 | @Override 567 | public void onAnimationUpdate(ValueAnimator animation) { 568 | scroll((int) animation.getAnimatedValue(), isListening); 569 | } 570 | }); 571 | if (listener != null) { 572 | animator.addListener(listener); 573 | } 574 | animator.start(); 575 | } 576 | 577 | /** 578 | * @param y 滑动y的位置 579 | * @param isListening 是否监听滑动变化,如果为true,滑动的变化将回调到{@link OnHeaderStateListener} 580 | * 或{@link OnFooterStateListener}。 581 | */ 582 | private void scroll(int y, boolean isListening) { 583 | 584 | scrollTo(0, y); 585 | if (isListening) { 586 | int scrollOffset = Math.abs(y); 587 | 588 | if (mCurrentState == STATE_DOWN && mOnHeaderStateListener != null) { 589 | int height = getHeaderTriggerHeight(); 590 | mOnHeaderStateListener.onScrollChange(mHeaderView, scrollOffset, 591 | scrollOffset >= height ? 100 : scrollOffset * 100 / height); 592 | } 593 | 594 | if (mCurrentState == STATE_UP && mOnFooterStateListener != null && mHasMore) { 595 | int height = getFooterTriggerHeight(); 596 | mOnFooterStateListener.onScrollChange(mFooterView, scrollOffset, 597 | scrollOffset >= height ? 100 : scrollOffset * 100 / height); 598 | } 599 | } 600 | } 601 | 602 | /** 603 | * 是否要处理嵌套滑动。 604 | * 605 | * @return 606 | */ 607 | private boolean isNestedScroll() { 608 | return isRefreshingOrLoading() || !mHasMore; 609 | } 610 | 611 | /** 612 | * 处理嵌套滑动。 613 | * 614 | * @param dy 滑动偏移量 615 | */ 616 | private void nestedPreScroll(int dy) { 617 | if (mIsRefreshing) { 618 | int scrollY = getScrollY(); 619 | if (dy > 0 && scrollY < 0) { 620 | scrollBy(0, Math.min(dy, -scrollY)); 621 | } 622 | } 623 | 624 | if (mIsLoadingMore || !mHasMore) { 625 | int scrollY = getScrollY(); 626 | if (dy < 0 && scrollY > 0) { 627 | scrollBy(0, Math.max(dy, -scrollY)); 628 | } 629 | } 630 | } 631 | 632 | /** 633 | * 处理嵌套滑动。 634 | * 635 | * @param dy 滑动偏移量 636 | */ 637 | private void nestedScroll(int dy) { 638 | if (mIsRefreshing) { 639 | int height = getHeaderTriggerHeight(); 640 | int scrollY = getScrollY(); 641 | if (dy < 0 && scrollY > -height) { 642 | int offset = -getScrollY() - height; 643 | if (offset < 0) { 644 | scrollBy(0, Math.max(dy, offset)); 645 | } 646 | } 647 | } 648 | 649 | if (mIsLoadingMore || !mHasMore) { 650 | int height = getFooterTriggerHeight(); 651 | int scrollY = getScrollY(); 652 | if (dy > 0 && scrollY < height) { 653 | scrollBy(0, Math.min(dy, height - scrollY)); 654 | } 655 | } 656 | } 657 | 658 | /** 659 | * 手指松开时,监听滑动状态的初始工作 660 | */ 661 | private void initScrollListen() { 662 | if (getChildCount() >= 3) { 663 | oldOffsetY = computeVerticalScrollOffset(getChildAt(2)); 664 | } 665 | } 666 | 667 | int scrollVelocity = 0; 668 | 669 | 670 | /** 671 | * 监听手指松开时,屏幕的滑动状态 672 | * 673 | * @return 返回true表示正在滑动,继续监听;返回false表示滑动停止或者不需要监听。 674 | */ 675 | private boolean listenScrollChange() { 676 | if (getChildCount() >= 3) { 677 | int offsetY = getScrollTopOffset(); 678 | int interval = Math.abs(offsetY - oldOffsetY); 679 | if (interval > 0) { 680 | scrollVelocity = interval; 681 | oldOffsetY = offsetY; 682 | if (isNestedScroll()) { 683 | if (canPullDown() && mFlingOrientation == ORIENTATION_FLING_UP) { 684 | nestedScroll(-scrollVelocity); 685 | } else if (canPullUp() && mFlingOrientation == ORIENTATION_FLING_DOWN) { 686 | nestedScroll(scrollVelocity); 687 | } 688 | 689 | if (getScrollY() > 0 && mFlingOrientation == ORIENTATION_FLING_UP) { 690 | nestedPreScroll(-scrollVelocity); 691 | } else if (getScrollY() < 0 && mFlingOrientation == ORIENTATION_FLING_DOWN) { 692 | nestedPreScroll(scrollVelocity); 693 | } 694 | } 695 | return true; 696 | } else { 697 | // 滑动停止 698 | 699 | //滑动停止时,如果已经滑动到底部,自动触发加载更多 700 | if (mFlingOrientation == ORIENTATION_FLING_DOWN && mAutoLoadMore && pullLoadMore()) { 701 | autoLoadMore(); 702 | return false; 703 | } 704 | 705 | if (scrollVelocity > 30) { 706 | if (canPullDown() && mIsRefreshing && mFlingOrientation == ORIENTATION_FLING_UP) { 707 | int height = getHeaderTriggerHeight(); 708 | smoothScroll(getScrollY(), -height, (int) (1.0f * height * SCROLL_DELAY / scrollVelocity), 709 | false, null); 710 | } else if ((mIsLoadingMore || !mHasMore) && canPullUp() 711 | && mFlingOrientation == ORIENTATION_FLING_DOWN) { 712 | int height = getFooterTriggerHeight(); 713 | smoothScroll(getScrollY(), height, (int) (1.0f * height * SCROLL_DELAY / scrollVelocity), 714 | false, null); 715 | } 716 | } 717 | scrollVelocity = 0; 718 | return false; 719 | } 720 | } 721 | return false; 722 | } 723 | 724 | /** 725 | * 获取内容布局滑动到顶部的偏移量 726 | * 727 | * @return 728 | */ 729 | private int getScrollTopOffset() { 730 | if (getChildCount() >= 3) { 731 | View view = getChildAt(2); 732 | return computeVerticalScrollOffset(view); 733 | } 734 | return 0; 735 | } 736 | 737 | /** 738 | * 获取内容布局滑动到底部部的偏移量 739 | * 740 | * @return 741 | */ 742 | private int getScrollBottomOffset() { 743 | if (getChildCount() >= 3) { 744 | View view = getChildAt(2); 745 | return computeVerticalScrollRange(view) - computeVerticalScrollOffset(view) 746 | - computeVerticalScrollExtent(view); 747 | } 748 | return 0; 749 | } 750 | 751 | private int computeVerticalScrollOffset(View view) { 752 | try { 753 | Method method = View.class.getDeclaredMethod("computeVerticalScrollOffset"); 754 | method.setAccessible(true); 755 | return (int) method.invoke(view); 756 | } catch (Exception e) { 757 | e.printStackTrace(); 758 | } 759 | return view.getScrollY(); 760 | } 761 | 762 | private int computeVerticalScrollRange(View view) { 763 | try { 764 | Method method = View.class.getDeclaredMethod("computeVerticalScrollRange"); 765 | method.setAccessible(true); 766 | return (int) method.invoke(view); 767 | } catch (Exception e) { 768 | e.printStackTrace(); 769 | } 770 | return view.getHeight(); 771 | } 772 | 773 | private int computeVerticalScrollExtent(View view) { 774 | try { 775 | Method method = View.class.getDeclaredMethod("computeVerticalScrollExtent"); 776 | method.setAccessible(true); 777 | return (int) method.invoke(view); 778 | } catch (Exception e) { 779 | e.printStackTrace(); 780 | } 781 | return view.getHeight(); 782 | } 783 | 784 | /** 785 | * @param isRefresh 是否开启下拉刷新功能 默认开启 786 | */ 787 | public void setRefreshEnable(boolean isRefresh) { 788 | mIsRefresh = isRefresh; 789 | } 790 | 791 | /** 792 | * @param isLoadMore 是否开启上拉功能 默认不开启 793 | */ 794 | public void setLoadMoreEnable(boolean isLoadMore) { 795 | mIsLoadMore = isLoadMore; 796 | } 797 | 798 | /** 799 | * 还原 800 | */ 801 | private void restore(boolean isListener) { 802 | smoothScroll(getScrollY(), 0, 200, isListener, null); 803 | } 804 | 805 | /** 806 | * 通知刷新完成。它会回调{@link OnHeaderStateListener#onRetract(View, boolean)}方法 807 | * 808 | * @param isSuccess 是否刷新成功 809 | */ 810 | public void finishRefresh(boolean isSuccess) { 811 | if (mIsRefreshing) { 812 | mCurrentState = STATE_NOT; 813 | if (mOnHeaderStateListener != null) { 814 | mOnHeaderStateListener.onRetract(mHeaderView, isSuccess); 815 | } 816 | } 817 | postDelayed(new Runnable() { 818 | @Override 819 | public void run() { 820 | //平滑收起头部。 821 | smoothScroll(getScrollY(), 0, 200, false, null); 822 | mIsRefreshing = false; 823 | } 824 | }, 500); 825 | } 826 | 827 | /** 828 | * 通知加载更多完成。它会回调{@link OnFooterStateListener#onRetract(View, boolean)}方法 829 | * 请使用{@link #finishLoadMore(boolean, boolean)} 830 | */ 831 | @Deprecated 832 | //不推荐使用这个方法 因为同时调用它和hasMore(boolean)两个方法时,尾部无法显示“加载完成”的提示。推荐使用finishLoadMore(boolean hasMore); 833 | public void finishLoadMore() { 834 | //为了处理先调用finishLoadMore(),后调用hasMore(boolean)的情况;延时调用finishLoadMore(mHasMore); 835 | postDelayed(new Runnable() { 836 | @Override 837 | public void run() { 838 | finishLoadMore(true, mHasMore); 839 | } 840 | }, 0); 841 | } 842 | 843 | /** 844 | * 通知加载更多完成。它会回调{@link OnFooterStateListener#onRetract(View, boolean)}方法 845 | * 846 | * @param isSuccess 是否加载成功 847 | * @param hasMore 是否还有更多数据 848 | */ 849 | public void finishLoadMore(boolean isSuccess, final boolean hasMore) { 850 | if (mIsLoadingMore) { 851 | mCurrentState = STATE_NOT; 852 | if (mOnFooterStateListener != null) { 853 | mOnFooterStateListener.onRetract(mFooterView, isSuccess); 854 | } 855 | } 856 | // 处理尾部的收起。 857 | postDelayed(new Runnable() { 858 | @Override 859 | public void run() { 860 | mIsLoadingMore = false; 861 | hasMore(hasMore); 862 | if (getScrollBottomOffset() > 0) { 863 | // 如果有新的内容加载出来,就收起尾部,并把新内容显示出来。。 864 | if (getChildCount() >= 3) { 865 | View v = getChildAt(2); 866 | if (v instanceof AbsListView) { 867 | AbsListView listView = (AbsListView) v; 868 | listView.smoothScrollBy(getScrollY(), 0); 869 | } else { 870 | v.scrollBy(0, getScrollY()); 871 | } 872 | } 873 | scroll(0, false); 874 | } else if (mHasMore) { 875 | smoothScroll(getScrollY(), 0, 200, false, null); 876 | } 877 | } 878 | }, 500); 879 | } 880 | 881 | /** 882 | * 自动触发下拉刷新。只有启用了下拉刷新功能时起作用。 883 | */ 884 | public void autoRefresh() { 885 | if (!mIsRefresh || isRefreshingOrLoading()) { 886 | return; 887 | } 888 | post(new Runnable() { 889 | @Override 890 | public void run() { 891 | mCurrentState = STATE_DOWN; 892 | smoothScroll(getScrollY(), -getHeaderTriggerHeight(), 200, true, new AnimatorListenerAdapter() { 893 | @Override 894 | public void onAnimationEnd(Animator animation) { 895 | triggerRefresh(); 896 | } 897 | }); 898 | } 899 | }); 900 | } 901 | 902 | /** 903 | * 触发下拉刷新 904 | */ 905 | private void triggerRefresh() { 906 | if (!mIsRefresh || isRefreshingOrLoading()) { 907 | return; 908 | } 909 | 910 | mIsRefreshing = true; 911 | mCurrentState = STATE_NOT; 912 | scroll(-getHeaderTriggerHeight(), false); 913 | if (mOnHeaderStateListener != null) { 914 | mOnHeaderStateListener.onRefresh(mHeaderView); 915 | } 916 | 917 | if (mOnRefreshListener != null) { 918 | mOnRefreshListener.onRefresh(); 919 | } 920 | } 921 | 922 | /** 923 | * 自动触发上拉加载更多。只有在启用了上拉加载更多功能并且有更多数据时起作用。 924 | */ 925 | public void autoLoadMore() { 926 | if (isRefreshingOrLoading() || !mHasMore || !mIsLoadMore || mIsEmpty) { 927 | return; 928 | } 929 | post(new Runnable() { 930 | @Override 931 | public void run() { 932 | mCurrentState = STATE_UP; 933 | smoothScroll(getScrollY(), getFooterTriggerHeight(), 200, true, new AnimatorListenerAdapter() { 934 | @Override 935 | public void onAnimationEnd(Animator animation) { 936 | triggerLoadMore(); 937 | } 938 | }); 939 | } 940 | }); 941 | } 942 | 943 | /** 944 | * 触发上拉加载更多。 945 | */ 946 | private void triggerLoadMore() { 947 | if (isRefreshingOrLoading() || !mHasMore || !mIsLoadMore || mIsEmpty) { 948 | return; 949 | } 950 | mIsLoadingMore = true; 951 | mCurrentState = STATE_NOT; 952 | scroll(getFooterTriggerHeight(), false); 953 | if (mOnFooterStateListener != null) { 954 | mOnFooterStateListener.onRefresh(mFooterView); 955 | } 956 | if (mOnLoadMoreListener != null) { 957 | mOnLoadMoreListener.onLoadMore(); 958 | } 959 | } 960 | 961 | /** 962 | * 是否自动触发加载更多。只有在启用了上拉加载更多功能时起作用。 963 | * 964 | * @param autoLoadMore 如果为true,滑动到底部,自动触发加载更多 965 | */ 966 | public void setAutoLoadMore(boolean autoLoadMore) { 967 | mAutoLoadMore = autoLoadMore; 968 | } 969 | 970 | /** 971 | * 是否还有更多数据,只有为true是才能上拉加载更多. 972 | * 它会回调{@link OnFooterStateListener#hasMore(boolean)}方法。 973 | * 974 | * @param hasMore 默认为true。 975 | */ 976 | public void hasMore(boolean hasMore) { 977 | if (mHasMore != hasMore) { 978 | mHasMore = hasMore; 979 | if (mOnFooterStateListener != null) { 980 | mOnFooterStateListener.onHasMore(mFooterView, hasMore); 981 | } 982 | } 983 | } 984 | 985 | /** 986 | * 获取触发下拉刷新的下拉高度 987 | * 988 | * @return 989 | */ 990 | public int getHeaderTriggerHeight() { 991 | int height = mHeaderLayout.getHeight(); 992 | height = Math.max(height, mHeaderTriggerMinHeight); 993 | height = Math.min(height, mHeaderTriggerMaxHeight); 994 | return height; 995 | } 996 | 997 | /** 998 | * 获取触发上拉加载的上拉高度 999 | * 1000 | * @return 1001 | */ 1002 | public int getFooterTriggerHeight() { 1003 | int height = mFooterLayout.getHeight(); 1004 | height = Math.max(height, mFooterTriggerMinHeight); 1005 | height = Math.min(height, mFooterTriggerMaxHeight); 1006 | return height; 1007 | } 1008 | 1009 | /** 1010 | * 设置触发下拉刷新的最小高度。 1011 | * 1012 | * @param headerTriggerMinHeight 1013 | */ 1014 | public void setHeaderTriggerMinHeight(int headerTriggerMinHeight) { 1015 | mHeaderTriggerMinHeight = headerTriggerMinHeight; 1016 | } 1017 | 1018 | /** 1019 | * 设置触发下拉刷新的最大高度。 1020 | * 1021 | * @param headerTriggerMaxHeight 1022 | */ 1023 | public void setHeaderTriggerMaxHeight(int headerTriggerMaxHeight) { 1024 | mHeaderTriggerMaxHeight = headerTriggerMaxHeight; 1025 | } 1026 | 1027 | /** 1028 | * 设置触发上拉加载的最小高度。 1029 | * 1030 | * @param footerTriggerMinHeight 1031 | */ 1032 | public void setFooterTriggerMinHeight(int footerTriggerMinHeight) { 1033 | mFooterTriggerMinHeight = footerTriggerMinHeight; 1034 | } 1035 | 1036 | /** 1037 | * 设置触发上拉加载的最大高度。 1038 | * 1039 | * @param footerTriggerMaxHeight 1040 | */ 1041 | public void setFooterTriggerMaxHeight(int footerTriggerMaxHeight) { 1042 | mFooterTriggerMaxHeight = footerTriggerMaxHeight; 1043 | } 1044 | 1045 | /** 1046 | * 设置拉动阻力 (1到10) 1047 | * 1048 | * @param damp 1049 | */ 1050 | public void setDamp(int damp) { 1051 | if (damp < 1) { 1052 | mDamp = 1; 1053 | } else if (damp > 10) { 1054 | mDamp = 10; 1055 | } else { 1056 | mDamp = damp; 1057 | } 1058 | } 1059 | 1060 | /** 1061 | * 隐藏内容布局,显示空布局 1062 | */ 1063 | public void showEmpty() { 1064 | if (!mIsEmpty) { 1065 | mIsEmpty = true; 1066 | //显示空布局 1067 | if (getChildCount() > 3) { 1068 | getChildAt(3).setVisibility(VISIBLE); 1069 | } 1070 | //隐藏内容布局 1071 | if (getChildCount() > 2) { 1072 | getChildAt(2).setVisibility(GONE); 1073 | } 1074 | } 1075 | } 1076 | 1077 | /** 1078 | * 隐藏空布局,显示内容布局 1079 | */ 1080 | public void hideEmpty() { 1081 | if (mIsEmpty) { 1082 | mIsEmpty = false; 1083 | //隐藏空布局 1084 | if (getChildCount() > 3) { 1085 | getChildAt(3).setVisibility(GONE); 1086 | } 1087 | //显示内容布局 1088 | if (getChildCount() > 2) { 1089 | getChildAt(2).setVisibility(VISIBLE); 1090 | } 1091 | } 1092 | } 1093 | 1094 | /** 1095 | * 设置加载更多的监听,触发加载时回调。 1096 | * RefreshLayout默认没有启用上拉加载更多的功能,如果设置了OnLoadMoreListener,则自动启用。 1097 | * 1098 | * @param listener 1099 | */ 1100 | public void setOnLoadMoreListener(OnLoadMoreListener listener) { 1101 | mOnLoadMoreListener = listener; 1102 | if (listener != null) { 1103 | setLoadMoreEnable(true); 1104 | } 1105 | } 1106 | 1107 | /** 1108 | * 设置刷新监听,触发刷新时回调 1109 | * 1110 | * @param listener 1111 | */ 1112 | public void setOnRefreshListener(OnRefreshListener listener) { 1113 | mOnRefreshListener = listener; 1114 | } 1115 | 1116 | 1117 | //---------------- 监听接口 -------------------// 1118 | 1119 | /** 1120 | * 头部状态监听器 1121 | */ 1122 | public interface OnHeaderStateListener { 1123 | 1124 | /** 1125 | * 头部滑动变化 1126 | * 1127 | * @param headerView 头部View 1128 | * @param scrollOffset 滑动距离 1129 | * @param scrollRatio 从开始到触发阀值的滑动比率(0到100)如果滑动到达了阀值,就算再滑动,这个值也是100 1130 | */ 1131 | void onScrollChange(View headerView, int scrollOffset, int scrollRatio); 1132 | 1133 | /** 1134 | * 头部处于刷新状态 (触发下拉刷新的时候调用) 1135 | * 1136 | * @param headerView 头部View 1137 | */ 1138 | void onRefresh(View headerView); 1139 | 1140 | /** 1141 | * 刷新完成,头部收起 1142 | * 1143 | * @param headerView 头部View 1144 | * @param isSuccess 是否刷新成功 1145 | */ 1146 | void onRetract(View headerView, boolean isSuccess); 1147 | 1148 | } 1149 | 1150 | /** 1151 | * 尾部状态监听器 1152 | */ 1153 | public interface OnFooterStateListener { 1154 | 1155 | /** 1156 | * 尾部滑动变化 1157 | * 1158 | * @param footerView 尾部View 1159 | * @param scrollOffset 滑动距离 1160 | * @param scrollRatio 从开始到触发阀值的滑动比率(0到100)如果滑动到达了阀值,就算在滑动,这个值也是100 1161 | */ 1162 | void onScrollChange(View footerView, int scrollOffset, int scrollRatio); 1163 | 1164 | /** 1165 | * 尾部处于加载状态 (触发上拉加载的时候调用) 1166 | * 1167 | * @param footerView 尾部View 1168 | */ 1169 | void onRefresh(View footerView); 1170 | 1171 | /** 1172 | * 加载完成,尾部收起 1173 | * 1174 | * @param footerView 尾部View 1175 | * @param isSuccess 是否加载成功 1176 | */ 1177 | void onRetract(View footerView, boolean isSuccess); 1178 | 1179 | /** 1180 | * 是否还有更多(是否可以加载下一页) 1181 | * 1182 | * @param footerView 1183 | * @param hasMore 1184 | */ 1185 | void onHasMore(View footerView, boolean hasMore); 1186 | } 1187 | 1188 | /** 1189 | * 上拉加载监听器 1190 | */ 1191 | public interface OnLoadMoreListener { 1192 | void onLoadMore(); 1193 | } 1194 | 1195 | /** 1196 | * 下拉更新监听器 1197 | */ 1198 | public interface OnRefreshListener { 1199 | void onRefresh(); 1200 | } 1201 | } 1202 | --------------------------------------------------------------------------------