Android 滑动scrollBy()和scrollTo()两个方法的简单认识

涉及到滑动,就涉及到VIEW,大家都知道,Android的UI界面都是由一个一个的View以及View的派生类组成,View作为基类,而常用的布局里面的各种布局就是它派生出来的ViewGroup的子类,ViewGroup作为各个组件的容器搭建了整体的UI。以下是android UI的结构示示意图:

Android 滑动scrollBy()和scrollTo()两个方法的简单认识_第1张图片

查看源码

[java]  view plain  copy
  1. /** 
  2.  * Implement this to do your drawing. 
  3.  * 
  4.  * @param canvas the canvas on which the background will be drawn 
  5.  */  
  6. protected void onDraw(Canvas canvas) {  
  7. }  
可以发现, View的实现一般是通过绘制 onDraw方法 进行,如果你要改变它的界面可以重写 onDraw,达到你的效果,在源码中,你找不到    

[java]  view plain  copy
  1. public void addView(View child) {  
  2.         addView(child, -1);  
  3.     }  
这个方法,因为它不知道,只有在它的派生类例如 ViewGroup中会有这个方式去添加子布局。

而ViewGroup作为一个组件容器,它可以包含任何组件,可是你必须重写他的onLayout() 方法和 onMeasure()来设置容器布局的位置和绘制它的大小才能正常显示。

首先 ,我们必须明白在Android View视图是没有边界的,Canvas是没有边界的,只不过我们通过绘制特定的View时对 Canvas对象进行了一定的操作,例如 : translate(平移)、clipRect(剪切)等,以便达到我们的对该Canvas对象绘制的要求 ,我们可以将这种无边界的视图称为“视图坐标”-----它不受物理屏幕限制。通常我们所理解的一个Layout布局文件只是该视图的显示区域,超过了这个显示区域将不能显示到父视图的区域中 ,对应的,我们可以将这种有边界的视图称为“布局坐标”------ 父视图给子视图分配的布局(layout)大小。而且, 一个视图的在屏幕的起始坐标位于视图坐标起始处,如下图所示。

Android 滑动scrollBy()和scrollTo()两个方法的简单认识_第2张图片

其实是相对于父类视图的左上角坐标为原点(0,0),而不是整体ViewGroup的左上角为原点。

由于布局坐标只能显示特定的一块内容,所以我们只有移动布局坐标的坐标原点就可以将视图坐标的任何位置显示出来。

(注:例如cocos2D的布局就和android的布局坐标原点坐标不一样,是左下角会原点,所以会有所差异。)


这里就大致提下View和ViewGroup,(网上很多大神都对这块进行了分析,这里只是做了少量摘抄记录)目的是为了引出今天的主角scrollTo 和 scrollBy。


查看下源码可以发现:

[java]  view plain  copy
  1. "font-family:SimSun;font-size:14px;">  /** 
  2.      * The offset, in pixels, by which the content of this view is scrolled 
  3.      * horizontally. 
  4.      * {@hide} 
  5.      */  
  6.     @ViewDebug.ExportedProperty(category = "scrolling")  
  7.     protected int mScrollX;  
  8.     /** 
  9.      * The offset, in pixels, by which the content of this view is scrolled 
  10.      * vertically. 
  11.      * {@hide} 
  12.      */  
  13.     @ViewDebug.ExportedProperty(category = "scrolling")  
  14.     protected int mScrollY;  
  15.     /** 
  16.      * Return the scrolled left position of this view. This is the left edge of 
  17.      * the displayed part of your view. You do not need to draw any pixels 
  18.      * farther left, since those are outside of the frame of your view on 
  19.      * screen. 
  20.      * 
  21.      * @return The left edge of the displayed part of your view, in pixels. 
  22.      */  
  23.     public final int getScrollX() {  
  24.         return mScrollX;  
  25.     }  
  26.   
  27.     /** 
  28.      * Return the scrolled top position of this view. This is the top edge of 
  29.      * the displayed part of your view. You do not need to draw any pixels above 
  30.      * it, since those are outside of the frame of your view on screen. 
  31.      * 
  32.      * @return The top edge of the displayed part of your view, in pixels. 
  33.      */  
  34.     public final int getScrollY() {  
  35.         return mScrollY;  
  36.     }  

mScrollX:表示离视图起始位置的x水平方向的偏移量

mScrollY:表示离视图起始位置的y垂直方向的偏移量

分别通过getScrollX() 和getScrollY()方法获得。

注意:mScrollX和mScrollY指的并不是坐标,而是偏移量。


[java]  view plain  copy
  1. "font-family:SimSun;font-size:14px;"/** 
  2.      * Set the scrolled position of your view. This will cause a call to 
  3.      * {@link #onScrollChanged(int, int, int, int)} and the view will be 
  4.      * invalidated. 
  5.      * @param x the x position to scroll to 
  6.      * @param y the y position to scroll to 
  7.      */  
  8.     public void scrollTo(int x, int y) {  
  9.         if (mScrollX != x || mScrollY != y) {  
  10.             int oldX = mScrollX;  
  11.             int oldY = mScrollY;  
  12.             mScrollX = x;  
  13.             mScrollY = y;  
  14.             invalidateParentCaches();  
  15.             onScrollChanged(mScrollX, mScrollY, oldX, oldY);  
  16.             if (!awakenScrollBars()) {  
  17.                 postInvalidateOnAnimation();  
  18.             }  
  19.         }  
  20.     }  
  21.   
  22.     /** 
  23.      * Move the scrolled position of your view. This will cause a call to 
  24.      * {@link #onScrollChanged(int, int, int, int)} and the view will be 
  25.      * invalidated. 
  26.      * @param x the amount of pixels to scroll by horizontally 
  27.      * @param y the amount of pixels to scroll by vertically 
  28.      */  
  29.     public void scrollBy(int x, int y) {  
  30.         scrollTo(mScrollX + x, mScrollY + y);  
  31.     }  

从以上的代码可以看出, scrollTo 和 scrollBy区别,其实2者的效果是一样的。


scrollTo(int x,int y):

如果偏移位置发生了改变,就会给mScrollX和mScrollY赋新值,改变当前位置。

注意:x,y代表的不是坐标点,而是偏移量。

例如:

我要移动view到坐标点(100,100),那么我的偏移量就是(0,,0)  - (100,100) = (-100 ,-100)  ,我就要执行view.scrollTo(-100,-100),达到这个效果。


scrollBy(int x,int y):

从源码中看出,它实际上是调用了scrollTo(mScrollX + x, mScrollY + y);

mScrollX + x和mScrollY + y,即表示在原先偏移的基础上在发生偏移,通俗的说就是相对我们当前位置偏移。

根据父类VIEW里面移动,如果移动到了超出的地方,就不会显示。

查看上文中的示意图你就会知道大概。


下面通过一个小例子了解下这2个方法之间的的使用,

效果图如下:

Android 滑动scrollBy()和scrollTo()两个方法的简单认识_第3张图片

核心代码就是本文讲的2个方法。

scrollBy(int dx, int dy)主要用于滑屏操作,第一个参数dx代表滑屏后与滑屏前的x坐标之差,第二个参数dy同理。那下面我们来试试吧。

自定义View,使用scrollBy

首先我们新建了类DragView继承自Button

public class DragView extends Button
{
    private int mDownX;
    private int mDownY;

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

    public DragView(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public DragView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        setBackgroundColor(0x88FF0000);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                mDownX = (int) event.getX();
                mDownY = (int) event.getY();
                break;

            case MotionEvent.ACTION_MOVE:
                int mX = (int) event.getX();
                int mY = (int) event.getY();

                int dX = mX - mDownX;
                int dY = mY - mDownY;

                scrollBy(dX, dY);
                break;
        }
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

可以看到,我们设置了浅红色的背景,声明了两个全局变量,并重写了onTouchEvent方法,里面判断了单击和滑动事件,单击时记录x和y的坐标,赋值给mDownX和mDownY,滑动的时候也获取x和y的坐标,和单击时的坐标相减取得偏移量,调用scrollBy方法。

然后,我们在布局中使用:

"http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <com.sun.androidqyz.DragView
        android:layout_width="100dp"
        android:layout_height="100dp"/>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

很简单吧,效果图如下:

Android 滑动scrollBy()和scrollTo()两个方法的简单认识_第4张图片

咦,咋滑不动呢?(我是真的滑了,不是在滑动鼠标)先不管为啥拖不动。我们在布局中添加几个属性:

"http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <com.sun.androidqyz.DragView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:gravity="center"
        android:text="@string/app_name"/>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

可以看到,为自定义的View增加了Android:text属性,重新运行程序,看看效果如何:

Android 滑动scrollBy()和scrollTo()两个方法的简单认识_第5张图片

哈,现在是不是可以看到滑动的效果了呢?不对啊,我们明明自定义的View,现在为啥拖动的不是View而是View里面的字呢,啥子情况哦?

其实呢,在自定义View中直接调用scrollBy滑动的是View的Content内容,对于Button,它的Content就是文本,ImageView就是drawable了。

还有一个问题,不知你们发现没有?开始滑动时,可以看到鼠标是向上滑动的,按照人们的正常思维,那“AndroidQYZ”这几个字母也应该向上滑动才对,而现在是向下滑动。

这个就不太好理解了。做iOS开发的同学肯定用过UIScrolView,没错,看到这我才明白android和ios这么相似。其实是这样的:首先我们就要知道布局是没有边界的,就像很大一块画布,而手机屏幕就像是一个放大镜,放大了画布上的一小部分内容,当我们滑动屏幕时,画布是没有滑动的,滑动的是放大镜,就是我们的屏幕,可以这也说,当放大镜向上滑动时,我们就可以看到画布在向下滑动,这就是为啥鼠标明明向上滑动,而“AndroidQYZ”这几个字母却向下滑动的原因了,既然我们知道了为啥,那怎么修改呢?

我们先修改第二个问题,代码如下:

scrollBy(-dX, -dY);
  • 1
  • 1

so easy,没错,我们取了负值就可以了,不信?我们可以看看效果:

Android 滑动scrollBy()和scrollTo()两个方法的简单认识_第6张图片

可以看到,“AndroidQYZ”这几个字母已经跟随鼠标的移动而移动了,可还是没有让自定义View滑动呀,你这不是骗人嘛?不着急,之前说直接调用scrollBy滑动的是Content,这就简单了,我们直接调用自定义View的父View的scrollBy不就好了嘛,看下面:

((View) getParent()).scrollBy(-dX, -dY);
  • 1
  • 1

getParent()方法获取此View的ViewParent并强转为View,再调用scrollBy方法,要不要看看效果呢?就怕你们不信。

Android 滑动scrollBy()和scrollTo()两个方法的简单认识_第7张图片

哈哈,怎么样,可以滑动了吧。

你可能感兴趣的:(Android)