android中的scroll一般是调用View.scrollTo()函数实现的,另外有一个View.scrollBy()其实现也是View.scrollTo()。关于View.scrollTo(),下面有一些问题来探究一下,本文所查看的代码是android4.2.2的源代码。

  • 问:View.scrollTo()的原理是什么?
    答:在android.view.View.java中有两个变量mScrollX和mScrollY,两个变量的定义如下:
/**
 * The offset, in pixels, by which the content of this view is scrolled
 * horizontally.
 */
protected int mScrollX;

/**
 * The offset, in pixels, by which the content of this view is scrolled
 * vertically.
 */
protected int mScrollY;


在View的绘制过程中,会根据这两个值对里面的内容进行偏移。View.draw()中的代码片段如下:

int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);


在View.scrollTo()的作用就是就是改变这两个变量,从而引起View的content位置发生变化。

  • 问:Scroll是否引起Layout的改变?
    答:Scroll只是改变View.mScrollx和View.mScrollY,并且在draw()函数中,根据这两个偏移量进行绘制。所以只是影响draw(),不会引起Layout的改变。
  • 问:如果scroll只是在绘制的时候进行偏移,那么对touch事件的派发是否产生影响,View事件的响应是在scroll前的位置还是scroll后的位置?
    答:事件的分派是在ViewGroup.dispatchTouchEvent()中,查看源码,发现在寻找响应事件的子View时,会算上mScrollX和mScrollY的偏移。所以scroll是会对事件派发产生影响的。View事件的响应是在scroll后的位置。关键代码如下:
protected boolean isTransformedTouchPointInView(float x, float y, View child,
        PointF outLocalPoint) {
    float localX = x + mScrollX - child.mLeft;
    float localY = y + mScrollY - child.mTop;
    if (! child.hasIdentityMatrix() && mAttachInfo != null) {
        final float[] localXY = mAttachInfo.mTmpTransformLocation;
        localXY[0] = localX;
        localXY[1] = localY;
        child.getInverseMatrix().mapPoints(localXY);
        localX = localXY[0];
        localY = localXY[1];
    }
    final boolean isInView = child.pointInView(localX, localY);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(localX, localY);
    }
    return isInView;
}
  • 问:Scroller的作用是什么?
    答:Scroller的作用是帮助Scroll更加顺滑的。如果我们不是想在一段时间内,有A点顺滑的scroll到B点。这样我们就要计算每次draw应该偏移多少,才能达到顺滑的效果。Scroller就是用来帮助计算的。具体的计算方式是在Scroller.computeScrollOffset()函数中。代码如下:
public boolean computeScrollOffset() {
if (mFinished) {
    return false;
}

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

if (timePassed < mDuration) {
    switch (mMode) {
    case SCROLL_MODE:
        float x = timePassed * mDurationReciprocal;

        if (mInterpolator == null)
            x = viscousFluid(x); 
        else
            x = mInterpolator.getInterpolation(x);

        mCurrX = mStartX + Math.round(x * mDeltaX);
        mCurrY = mStartY + Math.round(x * mDeltaY);
        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 = 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;
}
  • 问:Scroll和ValueAnimator的区别是什么?
    答:ValueAnimator的本质是在一段时间内,按照一定的规律去改变一个值。而scroll就是改变mScrollX和mScrollY的值。所以理论上也可以用ValueAnimator来改变mScrollX和mScrollY,从而达到scroll的目的。
  • 问:Scroll和Animation的区别是什么?
    答:animation也是通过在draw函数里面实现的,但是animation只是改变了绘制的位置,并没有影响到事件的响应。所以当一个View从A点通过Animation动画移动到B点,View的事件继续在A点才能响应。