欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android View.scrollTo()和scrollBy()详解

程序员文章站 2022-05-05 08:55:21
...

一直以来View.scrollTo和View.scrollBy一直是困扰Android开发者的一个难题,网络上对于这个问题的解释也很多,但一般都是抄来抄去的,且大多文章都没有讲清楚
(其实有那么一些时刻是讲清楚了,但是又被他们绕进去了)

首先直接看着两个方法的实现:

    /**
     * Move the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the amount of pixels to scroll by horizontally
     * @param y the amount of pixels to scroll by vertically
     */
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

    /**
     * Set the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the x position to scroll to
     * @param y the y position to scroll to
     */
    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

显然,scrollBy是通过scrollTo来实现的,scrollTo的功能是将View的内容滚动到相对于View本身的特定位置.
而通过scrollBy的具体实现可以推断出,scrollBy的功能是将View的内容相对于View本身,在当前情况下,再在水平和竖直方向滚动x和y距离

对于上述代码,其中很重要的是mScrollX和mScrollY两个参数.
其意义在源码中表示如下:

    /**
     * The offset, in pixels, by which the content of this view is scrolled
     * horizontally.
     * {@hide}
     */
    protected int mScrollX;
    /**
     * The offset, in pixels, by which the content of this view is scrolled
     * vertically.
     * {@hide}
     */
    protected int mScrollY;

    /**
     * Set the horizontal scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param value the x position to scroll to
     */
    public void setScrollX(int value) {
        scrollTo(value, mScrollY);
    }

    /**
     * Set the vertical scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param value the y position to scroll to
     */
    public void setScrollY(int value) {
        scrollTo(mScrollX, value);
    }

    /**
     * Return the scrolled left position of this view. This is the left edge of
     * the displayed part of your view. You do not need to draw any pixels
     * farther left, since those are outside of the frame of your view on
     * screen.
     *
     * @return The left edge of the displayed part of your view, in pixels.
     */
    public final int getScrollX() {
        return mScrollX;
    }

    /**
     * Return the scrolled top position of this view. This is the top edge of
     * the displayed part of your view. You do not need to draw any pixels above
     * it, since those are outside of the frame of your view on screen.
     *
     * @return The top edge of the displayed part of your view, in pixels.
     */
    public final int getScrollY() {
        return mScrollY;
    }

mScrollX:表示view的内容离view起始位置的x水平方向的滚动偏移量
mScrollY:表示view的内容离view起始位置的y垂直方向的滚动偏移量

上述代码中可以看出,setScrollX/Y是通过scrollTo的方式来实现的,而在View的源代码中,只在scrollTo中对mScrollX/Y进行了赋值.

对于mScrollX/Y的值的具体解释如下:
1. View.mScrollX的值总是等于View的左边缘和View内容的左边缘在水平方向上的距离.View左边缘在view内容左边缘的右边时,mScrollX为正值,反之为负值.
2. View.mScrollY的值总是等于view的上边缘和view内容的上边缘在竖直方向上的距离.View上边缘在view内容上边缘的下边时,mScrollY为正值,反之为负值.

上面这一段话摘自《Android开发艺术探索》,经过验证也的确是如此(验证代码见文章末尾),但是比较难于理解和记忆(念起来都比较费劲),
而且,如果细心观察的话,可以发现:
既然说了是移动view的内容,那么view内容向右/下移动时,也就是向屏幕坐标系方向移动时,mScrollX应该为正值才对(这里假设view没有移动过,也就是mScrollX/Y=0),而上述结果完全与屏幕坐标系相反,这是为什么呢?
可惜的是,其原因并没有在源码中有很明显的体现(在View的源代码代码中,只在scrollTo中对mScrollX/Y进行了赋值,这个过程中会将新传入的x,y值赋给mScrollX/Y),
但是实际效果的确是这样.我也在网上找了一些资料,发现大多要么直接
选择忽略这个问题,将这当做理所当然,要么就说什么画布无限大之类的,在我看来,都无法解释为什么这里的正负符号与屏幕坐标系完全相反.
最终找到如下说法(出处已经找不到了…):

scroll本身滚动的就是内容,view本身的位置没有改变。至于正负关系的问题,可以这样解释:在移动平台中,要明确知道“滑动”与“滚动”的不同,具体来说,滑动和滚动的方向总是相反的.
滑动:英文翻译为translate,在Android的动画中有移动的动画(但是其中的参数直接使用的from->to的方式),表示将对象移动至目的地
滚动:英文翻译为scroll,其实我们通常看滚动条就可以看出来,当滚动条向下移动时,屏幕原本显示的内容向上移动;
当滚动条向左移动时,屏幕原本显示
的内容向右移动。这也就可以解释为什么scrollBy(int x, int y)中为什么x>0时,view的内容向左移动;y>0时,view的向上移动了。

上述说法是不是真的正确,Android本身是不是这么实现的我不得而知,但是其对于”为什么这里的正负符号与屏幕坐标系完全相反”这个问题的解释在结果完全相符合验证结果,
so…..

fact matters,coincidence doesn’t
如果它是事实,则巧合无关大雅

验证代码下载地址引自android 布局之滑动探究 scrollTo 和 scrollBy 方法使用说明

写完发现还有一篇博客降了scrollTo中滚动方向问题的博客:Android 浅谈scrollTo和scrollBy源码