返回介绍

3. 源码分析

发布于 2024-12-23 22:21:11 字数 9107 浏览 0 评论 0 收藏 0

我们依然通过调用流程来分析 Scroller 的实现:

1. 构造方法

public Scroller(Context context) {
  this(context, null);
}

public Scroller(Context context, Interpolator interpolator) {
  this(context, interpolator,
      context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
}

public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
  mFinished = true;
  if (interpolator == null) {
    mInterpolator = new ViscousFluidInterpolator();
  } else {
    mInterpolator = interpolator;
  }
  mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
  mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
  mFlywheel = flywheel;

  mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
}

最终都会调用最后一个构造方法。必须传入 Context 对象。可以传入自定义的 interpolator 和是否支持飞轮 flywheel 的功能,当然这两个并不是必须的。如果不传入 interpolator 会默认创建一个 ViscousFluidInterpolator ,从字面意义上看是一个粘性流体插值器。

对于 flywheel 是指是否支持在滑动过程中,如果有新的 fling() 方法调用是否累加加速度。如果不传默认在 2.3 以上都会支持。剩下就是初始化了一些用于计算的参数。这样就完成了 Scroller 的初始化了。下面我们来看看 startScroll() 方法的实现:

2. startScroll() 方法的实现

public void startScroll(int startX, int startY, int dx, int dy, int duration) {
  // mMode 分两种方式 1.滑动:SCROLL_MODE 2. 加速度滑动:FLING_MODE
  mMode = SCROLL_MODE;
  // 是否滑动结束 这里是开始所以设置为 false
  mFinished = false;
  // 滑动的时间
  mDuration = duration;
  // 开始的时间
  mStartTime = AnimationUtils.currentAnimationTimeMillis();
  // 开始滑动点的 X 坐标
  mStartX = startX;
  // 开始滑动点的 Y 坐标
  mStartY = startY;
  // 最终滑动到位置的 X 坐标
  mFinalX = startX + dx;
  // 最终滑动到位置的 Y 坐标
  mFinalY = startY + dy;
  // X 方向上滑动的偏移量
  mDeltaX = dx;
  // Y 方向上滑动的偏移量
  mDeltaY = dy;
  // 持续时间的倒数 最终用来计算得到插值器返回的值
  mDurationReciprocal = 1.0f / (float) mDuration;
}

很简单只是一些变量的赋值。根据我们前面使用方法里的分析,最终会调用 computeScrollOffset() 方法:

3. computeScrollOffset() 方法中 SCROLL_MODE 的实现

// 当你需要知道新的位置的时候调用这个方法,如果动画还未结束则返回 true
public boolean computeScrollOffset() {
  //如果已经结束 则直接返回 false
  if (mFinished) {
    return false;
  }
  //得到以及度过的时间
  int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

  //如果还在动画时间内
  if (timePassed < mDuration) {
    switch (mMode) {
      case SCROLL_MODE:
        // 根据 timePassed * mDurationReciprocal,从 mInterpolator 中取出当前需要偏移量的比例
        final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
        // 赋值给 mCurrX,mCurrY
        mCurrX = mStartX + Math.round(x * mDeltaX);
        mCurrY = mStartY + Math.round(x * mDeltaY);
        break;
      case FLING_MODE:
        ...
        break;
    }
  }
  else {
    mCurrX = mFinalX;
    mCurrY = mFinalY;
    mFinished = true;
  }
  return true;
}

首先的到当前时间与滑动开始时间的时间差,如果还在滑动时间内则通过插值器获得当前的进度并乘以总偏移量并赋值给 mCurrXmCurrY 。如果已经结束则直接将 mFinalXmFinalY 赋值并将 mFinished 设置�为 true 。所以这样我们就能通过 getCurrX()getCurrY() 来得到对应的 mCurrXmCurrY 来做相应的处理了。整个 Scroll 的过程就是这样了。

4. fling() 方法的实现

  public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY) {
    // 如果前一次滑动还未结束,又调用了新的 fling() 方法时,
    // 则累加相同方向上加速度
    if (mFlywheel && !mFinished) {
      float oldVel = getCurrVelocity();

      float dx = (float) (mFinalX - mStartX);
      float dy = (float) (mFinalY - mStartY);
      float hyp = FloatMath.sqrt(dx * dx + dy * dy);

      float ndx = dx / hyp;
      float ndy = dy / hyp;

      float oldVelocityX = ndx * oldVel;
      float oldVelocityY = ndy * oldVel;
      if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
          Math.signum(velocityY) == Math.signum(oldVelocityY)) {
        velocityX += oldVelocityX;
        velocityY += oldVelocityY;
      }
    }

    //设置为 FLING_MODE
    mMode = FLING_MODE;
    mFinished = false;
    //根据勾股定理获得总加速度
    float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);

    mVelocity = velocity;
    // 通过加速度得到滑动持续时间
    mDuration = getSplineFlingDuration(velocity);
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    mStartX = startX;
    mStartY = startY;

    float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
    float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;

    double totalDistance = getSplineFlingDistance(velocity);
    mDistance = (int) (totalDistance * Math.signum(velocity));

    mMinX = minX;
    mMaxX = maxX;
    mMinY = minY;
    mMaxY = maxY;

    mFinalX = startX + (int) Math.round(totalDistance * coeffX);
    // Pin to mMinX <= mFinalX <= mMaxX
    mFinalX = Math.min(mFinalX, mMaxX);
    mFinalX = Math.max(mFinalX, mMinX);

    mFinalY = startY + (int) Math.round(totalDistance * coeffY);
    // Pin to mMinY <= mFinalY <= mMaxY
    mFinalY = Math.min(mFinalY, mMaxY);
    mFinalY = Math.max(mFinalY, mMinY);
  }

依然是为计算需要的各种变量赋值。因为引入了加速度的概念所以变得相对复杂,首先先判断了如果一次滑动未结束又触发另一次滑动时,是否需要累加加速度。然后是设置 mModeFLING_MODE 。然后根据 velocityXvelocityY 算出总的加速度 velocity ,紧接着算出这个加速度下可以滑动的距离 mDistance 。最后再通过 xy 方向上的加速度比值以及我们设定的最大值和最小值来给 mFinalXmFinalY 赋值。赋值结束后,通过调用 invalidate() ,最终依然会调用 computeScrollOffset() 方法:

5. computeScrollOffset() 方法中 FLING_MODE 的实现

  public boolean computeScrollOffset() {
    if (mFinished) {
      return false;
    }

    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

    if (timePassed < mDuration) {
      switch (mMode) {
        case SCROLL_MODE:
          ...
          break;
        case FLING_MODE:
          // 当前已滑动的时间与总滑动时间的比值
          final float t = (float) timePassed / mDuration;
          final int index = (int) (NB_SAMPLES * t);
          // 距离系数
          float distanceCoef = 1.f;
          // 加速度系数
          float velocityCoef = 0.f;
          if (index < NB_SAMPLES) {
            final float t_inf = (float) index / NB_SAMPLES;
            final float t_sup = (float) (index + 1) / NB_SAMPLES;
            final float d_inf = SPLINE_POSITION[index];
            final float d_sup = SPLINE_POSITION[index + 1];
            velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
            distanceCoef = d_inf + (t - t_inf) * velocityCoef;
          }

          // 计算出当前的加速度
          mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
          // 计算出当前的 mCurrX 与 mCurrY
          mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
          // Pin to mMinX <= mCurrX <= mMaxX
          mCurrX = Math.min(mCurrX, mMaxX);
          mCurrX = Math.max(mCurrX, mMinX);

          mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
          // Pin to mMinY <= mCurrY <= mMaxY
          mCurrY = Math.min(mCurrY, mMaxY);
          mCurrY = Math.max(mCurrY, mMinY);

          // 如果到达了终点 则结束
          if (mCurrX == mFinalX && mCurrY == mFinalY) {
            mFinished = true;
          }

          break;
      }
    }
    else {
      mCurrX = mFinalX;
      mCurrY = mFinalY;
      mFinished = true;
    }
    return true;
  }

由于 fling() 方法中将 mMode 赋值为 FLING_MODE 。所以我们直接来看 FLING_MODE 中的代码。可以看出根据当前滑动时间与总滑动时间的比例。再根据一个 SPLINE_POSITION 数组计算出了距离系数 distanceCoef 与加速度系数 velocityCoef 。再根据这两个系数计算出当前加速度与当前的 mCurrXmCurrY 。关于 SPLINE_POSITION 的初始化是在下面的静态代码块里赋值的:

  static {
    float x_min = 0.0f;
    float y_min = 0.0f;
    for (int i = 0; i < NB_SAMPLES; i++) {
      final float alpha = (float) i / NB_SAMPLES;

      float x_max = 1.0f;
      float x, tx, coef;
      while (true) {
        x = x_min + (x_max - x_min) / 2.0f;
        coef = 3.0f * x * (1.0f - x);
        tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
        if (Math.abs(tx - alpha) < 1E-5) break;
        if (tx > alpha) x_max = x;
        else x_min = x;
      }
      SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;

      float y_max = 1.0f;
      float y, dy;
      while (true) {
        y = y_min + (y_max - y_min) / 2.0f;
        coef = 3.0f * y * (1.0f - y);
        dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
        if (Math.abs(dy - alpha) < 1E-5) break;
        if (dy > alpha) y_max = y;
        else y_min = y;
      }
      SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
    }
    SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
  }

我并没有看懂这段代码的实际意义。网上也没有找到比较清晰的解释。通过 debug 得知 SPLINE_POSITION 是一个长度为 101 并且从 0-1 递增数组。猜想这应该是一个函数模型并且最终用于计算出滑动过程中的加速度与位置。至此 Scroller 的两个主要方法的实现我们就分析完了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文