PullToRefresh 的源码分析

前言

上篇文章介绍了 PullToRefresh 的继承关系。本片开始将介绍 PullToRefresh 的源码。由于时间的关系,关于PullToRefresh 的源码笔者也将分成两篇来介绍。本篇文章主要介绍 PullToRefresh 的下拉刷新是如何实现的。在下一篇中,将介绍如何扩展 PullToRefresh,并编写 demo。

PullToRefreshBase

上篇文章中我们知道 IPullToRefresh 是 PullToRefresh 的基类,PullToRefreshBase 实现了 IPullToRefresh。所以我猜测 PullToRefresh 的下拉刷新是在 PullToRefreshBase 中实现的。要实现下拉刷新的效果就牵扯到 View 的事件传递机制。所以我们先从 onInterceptTouchEvent 、onTouchEvent 和 dispatchTouchEvent 方法分析。但是在 PullToRefreshBase 中只有 onInterceptTouchEvent 和 onTouchEvent 。我们先看 onInterceptTouchEvent。

onInterceptTouchEvent

    @Override public final boolean onInterceptTouchEvent(MotionEvent event) {

      if (!isPullToRefreshEnabled()) {
        return false;
      }

      final int action = event.getAction();

      if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
        mIsBeingDragged = false;
        return false;
      }

      if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
        return true;
      }

      switch (action) {
        case MotionEvent.ACTION_MOVE: {
          // If we're refreshing, and the flag is set. Eat all MOVE events
          //if (!isCanMove){
          //    break;
          //}
          if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
            return true;
          }

          if (isReadyForPull()) {
            final float y = event.getY(), x = event.getX();
            final float diff, oppositeDiff, absDiff;

            // We need to use the correct values, based on scroll
            // direction
            switch (getPullToRefreshScrollDirection()) {
              case HORIZONTAL:
                diff = x - mLastMotionX;
                oppositeDiff = y - mLastMotionY;
                break;
              case VERTICAL:
              default:
                diff = y - mLastMotionY;
                oppositeDiff = x - mLastMotionX;
                break;
            }
            absDiff = Math.abs(diff);

            if (absDiff > mTouchSlop && (!mFilterTouchEvents || absDiff > Math.abs(oppositeDiff))) {
              if (mMode.showHeaderLoadingLayout() && diff >= 1f && isReadyForPullStart()) {
                mLastMotionY = y;
                mLastMotionX = x;
                mIsBeingDragged = true;
                if (mMode == Mode.BOTH) {
                  mCurrentMode = Mode.PULL_FROM_START;
                }
              } else if (mMode.showFooterLoadingLayout() && diff <= -1f && isReadyForPullEnd()) {
                mLastMotionY = y;
                mLastMotionX = x;
                mIsBeingDragged = true;
                if (mMode == Mode.BOTH) {
                  mCurrentMode = Mode.PULL_FROM_END;
                }
              }
            }
          }
          break;
        }
        case MotionEvent.ACTION_DOWN: {
          if (isReadyForPull()) {
            mLastMotionY = mInitialMotionY = event.getY();
            mLastMotionX = mInitialMotionX = event.getX();
            mIsBeingDragged = false;
          }
          break;
        }
      }

      return mIsBeingDragged;
    }

在 onInterceptTouchEvent 方法中,主要是判断是否满足下拉或者上拉的条件。其中 isReadyForPullStart() 方法和 isReadyForPullEnd() 方法都是在具体或者某一类的刷新控件中实现的,他们返回 true 的条件就是滑动到顶端或者低端。比如 PullToRefreshAdapterViewBase 中,PullToRefreshAdapterViewBase 是 AbsListView 的子类现实下拉刷新时的父类。如果满足下拉或者上拉的条件时 mIsBeingDragged 为 true。即触摸事件被 PullToRefreshBase 拦截。此时触摸事件将有 PullToRefreshBase 的 onTouchEvent 来处理。接下来请看 onTouchEvent。

onTouchEvent

在 onTouchEvent 的 ACTION_MOVE 分之里会调用 pullEvent 方法来处理 ACTION_MOVE 事件,实现滑动的效果。

    private void pullEvent() {
      final int newScrollValue;
      final int itemDimension;
      final float initialMotionValue, lastMotionValue;

      switch (getPullToRefreshScrollDirection()) {
        case HORIZONTAL:
          initialMotionValue = mInitialMotionX;
          lastMotionValue = mLastMotionX;
          break;
        case VERTICAL:
        default:
          initialMotionValue = mInitialMotionY;
          lastMotionValue = mLastMotionY;
          break;
      }

      switch (mCurrentMode) {
        case PULL_FROM_END:
          newScrollValue = Math.round(Math.max(initialMotionValue - lastMotionValue, 0) / FRICTION);
          itemDimension = getFooterSize();
          break;
        case PULL_FROM_START:
        default:
          newScrollValue = Math.round(Math.min(initialMotionValue - lastMotionValue, 0) / FRICTION);
          itemDimension = getHeaderSize();
          break;
      }

      setHeaderScroll(newScrollValue);

      if (newScrollValue != 0 && !isRefreshing()) {
        float scale = Math.abs(newScrollValue) / (float) itemDimension;
        switch (mCurrentMode) {
          case PULL_FROM_END:
            mFooterLayout.onPull(scale);
            break;
          case PULL_FROM_START:
          default:
            mHeaderLayout.onPull(scale);
            break;
        }

        if (mState != State.PULL_TO_REFRESH && itemDimension >= Math.abs(newScrollValue)) {
          setState(State.PULL_TO_REFRESH);
        } else if (mState == State.PULL_TO_REFRESH && itemDimension < Math.abs(newScrollValue)) {
          setState(State.RELEASE_TO_REFRESH);
        }
      }
    }

第 30 行,实现了 PullToRefreshBase 的滑动,是使用 View 的 scrollTo 方法来实现的。

第 32 到 42 行,调用相应得头部或者底部布局的 onPull 方法实现滑动时的动画效果。

第 44 到 48 行,对滑动状态的改变 mState = State.RELEASE_TO_REFRESH。

接下来看 onTouchEvent 的 ACTION_UP 分支

    case MotionEvent.ACTION_UP: {
      if (mIsBeingDragged) {
        mIsBeingDragged = false;

        if (mState == State.RELEASE_TO_REFRESH && (null != mOnRefreshListener
              || null != mOnRefreshListener2)) {
          setState(State.REFRESHING, true);
          return true;
        }

        // If we're already refreshing, just scroll back to the top
        if (isRefreshing()) {
          smoothScrollTo(0);
          return true;
        }

        // If we haven't returned by here, then we're not in a state
        // to pull, so just reset
        setState(State.RESET);

        return true;
      }
      break;
    }

第 7 行,条件满足调用 setState 方法。在 setState 方法里 State.REFRESHING 分支将调用 onRefreshing 方法。在 onRefreshing 方法里会监听是否滑动到了头部的顶端或者底部的底端。如果是,将调用 callRefreshListener 方法。请看 callRefreshListener 方法。

    private void callRefreshListener() {
      if (null != mOnRefreshListener) {
        mOnRefreshListener.onRefresh(this);
      } else if (null != mOnRefreshListener2) {
        if (mCurrentMode == Mode.PULL_FROM_START) {
          mOnRefreshListener2.onPullDownToRefresh(this);
        } else if (mCurrentMode == Mode.PULL_FROM_END) {
          mOnRefreshListener2.onPullUpToRefresh(this);
        }
      }
    }

在 callRefreshListener 方法中调用了一系列的刷新的监听者的回调方法。这些监听者正是我们自己实现的。在这些方法里可以完成我们需要的刷新操作,比如,重写请求数据。当我们完成这些操作后要主动调用 onRefreshComplete 方法。在 onRefreshComplete 方法中回调用状态重置的方法。重置 PullToRefresh 的状态。

结语

本篇文章就介绍到这里,下篇文章将介绍如何扩展 PullToRefresh。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值