android Scroller类的理解

Scroller

一个特例:

 **点击Button后可能View没有移动,要关闭硬件加速,这段代码中int detalX = (int) (event.getX() - downX)要更正。

demo下载

 1 //执行beginScroll() 方法,第一个子View将会滑动到制定位置
 2 //若用手指在MyViewGroup上滑动,子View根据手指滑动的距离来滑动相应的距离
 3 //以为scrollTo , scrollBy 移动的是子View,所以在第一个子View上面包裹了一个
 4 //父View Linearlayout,以便执行scrollTo 的时候不是将第一个子view里面的内容移动,而是
 5 //将第一个子View进行移动
 6 public class MyViewGroup extends LinearLayout {
 7 
 8     private boolean s1 = true;
 9     Scroller scroller = null;
10     private int downX, downY;
11     private View oneView;
12 
13     public MyViewGroup(Context context, AttributeSet attrs) {
14         super(context, attrs);
15         scroller = new Scroller(context);
16         setOrientation(LinearLayout.VERTICAL);
17     }
18 
19     /**
20      * onTouch , invalidate(), postInvalidate()都会触发computeScroll()方法
21      * Scroller的startScroll要执行出动画效果是要依靠computeScroll()方法的,
22      * 在该方法中调用Scroller.computeScrollOffset()来判断Scroller滑动完成没有
23      * 若没有滑动完成继续执行computeScroll(),在computeScroll()执行postInvalidate()
24      * 来循环调用computeScroll(),就形成了滑动动画效果了。
25      * 说白了Scroller只是记录了滑动信息,并不是它执行了滑动,正真的滑动效果是在computeScroll()执行的
26      * ,通过scroller.getCurrX()来获取当前滑动的值,用这个值来进行自己想达到的效果
27      */
28     @Override
29     public void computeScroll() {
30 
31         if (scroller.computeScrollOffset()) {
32             oneView.scrollTo(scroller.getCurrX(), 0);
33             postInvalidate();
34         }
35 
36     }
37 
38     @Override
39     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
40         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
41         oneView = getChildAt(0);
42     }
43 
44     public void beginScroll() {
45         if (!s1) {
46             scroller.startScroll(0, 0, 0, 0, 1000);
47             s1 = true;
48         } else {
49             scroller.startScroll(0, 0, -500, 0, 1000);
50             s1 = false;
51         }
52         postInvalidate();
53 
54     }
55 
56     @Override
57     public boolean onTouchEvent(MotionEvent event) {
58 
59         switch (event.getAction()) {
60         case MotionEvent.ACTION_DOWN:
61             System.out.println("MotionEvent.ACTION_DOWN");
62             requestDisallowInterceptTouchEvent(true);
63             downX = (int) event.getX();
64             downY = (int) event.getY();
65             return true;
66         case MotionEvent.ACTION_MOVE:
67             System.out.println("MotionEvent.ACTION_MOVE");
68             int detalX = (int) (event.getY() - downY);
69             oneView.scrollTo(detalX, 0);
70             break;
71         case MotionEvent.ACTION_UP:
72             System.out.println("当会不会执行");
73             break;
74         }
75 
76         // 将事件交给自定义的MyViewGroup处理 ,
77         // 如果返回false或者super.onTouch(event)则直接返回到顶端View去了
78         // 自定义的MyViewGroup的后续事件也就不会执行了.
79         return super.onTouchEvent(event);
80     }
81 }
View Code

 

 

1.在android 当中实现滑动功能的时候往往是要用来Scroller这个类的,但这个类的运行方式很特别,它不是直接和要滚动view绑定在一起的,而是通过子view和父view这层关系来发挥作用的

1、button点击不能移动,这是因为android 4.0默认打开了硬件加速,如果想让button移动,请在AndroidManifest的Application标签或者activity标签中加 入:android:hardwareAccelerated="false"

来看看api是怎么说Scroller的:

This class encapsulates scrolling. The duration of the scroll can be passed in the constructor and specifies the maximum time that the scrolling animation should take. Past this time,

the scrolling is automatically moved to its final stage and computeScrollOffset() will always return false to indicate that scrolling is over

其中包含的一些方法:

 

主要用到的方法是:

computeScrollOffset():当view在滚动的时候返回的是true,滚动完成后返回的是false, 就是通过这个方法来判断view的滚动是否完成的

还有一些就是用于获取当前滚动view的x 和 y的坐标的

startScroll((int startX, int startY, int dx, int dy, int duration):设定view的滚动参数,

startX : X 的起始位置。

startY : Y 的起始位置。

dx : 水平偏移距离

dy : 垂直偏移距离

duration : 到达指定距离所需要的时间

3.要想利用Scroller这个类就必须要了解的一个重要方法:

 

1).computeScroll()

 

 

1  /** 
2  * Called by a parent to request that a child update its values for mScrollX 
3  * and mScrollY if necessary. This will typically be done if the child is 
4  * animating a scroll using a {@link android.widget.Scroller Scroller} 
5  * object. 
6  */  
7 public void computeScroll()  
8 {  
9 }  

 

这是一个空函数,就需要我们去实现它,对它的实现就设定了对view的怎样滚动进行实现。

还有个android源代码中的方法:

2).dispatchDraw()

 1 @Override  
 2 protected void dispatchDraw(Canvas canvas) {  
 3   
 4   
 5             for (int i = 0; i < count; i++) {  
 6             final View child = children[i];  
 7             if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)  
 8   
 9             {  
10                 more |= drawChild(canvas, child, drawingTime);  
11             }  
从这段代码可以知道,父view会调用drawChild对每个子view进行绘制。
在下面的例子中, ContentLinearLayout的孩子有2个,是2个MyLinearLayout类型的实例
3).drawChild对子view进行绘制
 1 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {  
 2   
 3             ................  
 4   
 5             ................  
 6   
 7            child.computeScroll();  
 8   
 9             ................  
10   
11             ................  
12   
13 }  

在父容器重画自己的孩子时,它会调用孩子的 computScroll方法,也就是说例程中的ContentLinearLayout在调用dispatchDraw()函数时会调用 MyLinearLayout的computeScroll方法。

这个computeScroll()函数正是我们大展身手的地方,在这个函数里我们可以去取得事先设置好的成员变量mScroller中的位置信息、速度信息等等,用这些参数来做我们想做的事情

4.下面就通过一个例子来说讲解Scroller:

  1 public class MainActivity extends Activity {
  2 
  3     LinearLayout lay1, lay2, lay0, lay3, lay4;
  4     private Scroller mScroller;
  5 
  6     @Override
  7     public void onCreate(Bundle savedInstanceState) {
  8         super.onCreate(savedInstanceState);
  9         mScroller = new Scroller(this);
 10         lay1 = new MyLinearLayout(this);
 11         lay2 = new MyLinearLayout(this);
 12 
 13         lay1.setBackgroundColor(this.getResources().getColor(
 14                 android.R.color.darker_gray));
 15         lay2.setBackgroundColor(this.getResources().getColor(
 16                 android.R.color.white));
 17         lay0 = new ContentLinearLayout(this);
 18         lay0.setOrientation(LinearLayout.VERTICAL);
 19         LinearLayout.LayoutParams p0 = new LinearLayout.LayoutParams(
 20                 LinearLayout.LayoutParams.MATCH_PARENT,
 21                 LinearLayout.LayoutParams.MATCH_PARENT);
 22         this.setContentView(lay0, p0);
 23 
 24         LinearLayout.LayoutParams p1 = new LinearLayout.LayoutParams(
 25                 LinearLayout.LayoutParams.MATCH_PARENT,
 26                 LinearLayout.LayoutParams.MATCH_PARENT);
 27         p1.weight = 1;
 28         lay0.addView(lay1, p1);
 29         LinearLayout.LayoutParams p2 = new LinearLayout.LayoutParams(
 30                 LinearLayout.LayoutParams.MATCH_PARENT,
 31                 LinearLayout.LayoutParams.MATCH_PARENT);
 32         p2.weight = 1;
 33         lay0.addView(lay2, p2);
 34         MyButton btn1 = new MyButton(this);
 35         MyButton btn2 = new MyButton(this);
 36         MyButton btn3 = new MyButton(this);
 37         btn1.setText("btn in layout1");
 38         btn2.setText("btn in layout2");
 39         btn3.setText("btn in layout3");
 40 
 41         btn1.setOnClickListener(new OnClickListener() {
 42             @Override
 43             public void onClick(View v) {
 44                 mScroller.startScroll(0, 0, -30, -30, 50);
 45             }
 46         });
 47         btn2.setOnClickListener(new OnClickListener() {
 48             @Override
 49             public void onClick(View v) {
 50                 mScroller.startScroll(20, 20, -500, -500, 500);
 51             }
 52         });
 53         lay1.addView(btn1);
 54         lay1.addView(btn3);
 55         lay2.addView(btn2);
 56     }
 57 
 58     class MyButton extends Button {
 59         public MyButton(Context ctx) {
 60             super(ctx);
 61         }
 62 
 63         @Override
 64         protected void onDraw(Canvas canvas) {
 65             super.onDraw(canvas);
 66             System.out.println("MyButton ====" + this.toString()
 67                     + " onDraw------");
 68 
 69         }
 70     }
 71 
 72     class MyLinearLayout extends LinearLayout {
 73         public MyLinearLayout(Context ctx) {
 74             super(ctx);
 75         }
 76 
 77         @Override
 78         /** 
 79          * Called by a parent to request that a child update its values for mScrollX 
 80          * and mScrollY if necessary. This will typically be done if the child is 
 81          * animating a scroll using a {@link android.widget.Scroller Scroller} 
 82          * object. 
 83          */
 84         public void computeScroll() {
 85 
 86             System.out.println(this.toString() + " computeScroll-----------");
 87 
 88             if (mScroller.computeScrollOffset())// 如果mScroller没有调用startScroll,这里将会返回false。
 89             {
 90                 // 因为调用computeScroll函数的是MyLinearLayout实例,
 91                 // 所以调用scrollTo移动的将是该实例的孩子,也就是MyButton实例
 92                 scrollTo(mScroller.getCurrX(), 0); // 意思是儿子的view的怎么变化去找老子computeScroll函数里面的设置
 93                 System.out.println("getCurrX = " + mScroller.getCurrX());
 94 
 95                 // 当点击lay1中的btn in layout1就会通知系统重绘制,点击btn in layout3的时候就只绘制一次的
 96                 getChildAt(0).invalidate();   
 97             }
 98         }
 99     }
100 
101     class ContentLinearLayout extends LinearLayout {
102         public ContentLinearLayout(Context ctx) {
103             super(ctx);
104         }
105 
106         public void computeScroll() {
107             System.out.println("ContentLinearLayout中的"
108                     + " computeScroll-----------");
109         }
110 
111         @Override
112         protected void dispatchDraw(Canvas canvas) {
113             System.out.println("contentview dispatchDraw");
114             super.dispatchDraw(canvas);
115         }
116 
117     }
118 
119 }

运行后UI 的效果是:

5.现在就分别对Button进行点击,来进行说明:

  1).当点击btn in layout1的时候

 

  2)当点击btn in layout2的时候(太多了就截取了一部分)

  3)当点击btn in layout3的时候:

现在对上面的结果进行一下分析:当btn in layout1的时候首先是调用ContentLinearLayout里面computScroll,然后再调用ConentLinearLayout的dispatchDraw进行子View的

分发绘制。btn in layout1在lay1中则调用lay1中的computScroll方法,调用完这个方法后继而执行在lay1中子view的onDraw进行绘制。 因为lay1的computScroll里面的getChildAt(0).invalidate();   让点击btn in layout1时候通知系统重绘制,而点击btn in layout3的时候没哟通知系统重绘制,所以从上面点击显示结果中btn in layout3的结果比btn in layout1的结果少很多。点击btn in layout2的理解和上面是一样的

引用别人的一段话:

既然重绘请求已发出了,那么整个View系统就会来一次自上而下的绘制了,首先输出的Log 就是“contentview dispatchDraw”了,它将绘制需要重绘的孩子(lay1和lay2中的一个),接着会调用drawChild,使得computeScroll 函数被触发(drawChild里面会调用child.computeScroll()),于是,lay1或者lay2就会以mScroller的位置信 息为依据来调用scrollTo了,它的孩子btn1或者btn2就会被移动了。之后又调用了getChildAt(0).invalidate(),这 将导致系统不断重绘,直到startScroll中设置的时间耗尽mScroller.computeScrollOffset()返回false才停下

如果你想控制lay1和lay2的移动可以在ContentLinearout的computScroll中利用scrollTo()进行移动

思想总结:1.绘制是至上而下的 2.要移动子View就要在父view的computScroll中写方法。

代码下载:代码

 

下面内容的参考地址:

http://www.w3c.com.cn/%E5%9B%BE%E8%A7%A3android-view%E7%9A%84scrolltoscrollbygetscrollx-getscrolly

 Android系统手机屏幕的左上角为坐标系,同时y轴方向与笛卡尔坐标系的y轴方向想反。通过提供的api如getLeftgetTopgetBottomgetRight可以获得控件在parent中的相对位置。同时,也可以获得控件在屏幕中的绝对位置,详细用法可参考android应用程序中获取view的位置

当我们 编写一些自定义的滑动控件时,会用到一些api如scrollTo(),scrollBy(),getScrollX(), getScrollY()。由于常常会对函数getScrollX(), getScrollY()返回的值的含义产生混淆,尤其是正负关系,因此本文将使用几幅图来对这些函数进行讲解以方便大家记忆。

注意:调用View的scrollTo()和scrollBy()是用于滑动View中的内容,而不是把某个View的位置进行改变。如果想改变莫个View在屏幕中的位置,可以使用如下的方法。

调用public void offsetLeftAndRight(int offset)用于左右移动方法或public void offsetTopAndBottom(int offset)用于上下移动。

                 如:button.offsetLeftAndRignt(300)表示将button控件向左移动300个像素。

 

scrollTo(int x, int y) 是将View中内容滑动到相应的位置,参考的坐标系原点为parent View的左上角。

       调用scrollTo(100, 0)表示将View中的内容移动到x = 100, y = 0的位置,如下图所示。注意,图中黄色矩形区域表示的是一个parent View,绿色虚线矩形为parent view中的内容。一般情况下两者的大小一致,本文为了显示方便,将虚线框画小了一点。图中的黄色区域的位置始终不变,发生位置变化的是显示的内容。

 同理,scrollTo(0, 100)的效果如下图所示:

scrollTo(100, 100)的效果图如下:

 

若函数中参数为负值,则子View的移动方向将相反。

scrollBy(int x, int y)其实是对scrollTo的包装,移动的是相当位置。 scrollTo(int x, int y)的源码和scrollBy(int x, int y)源码如下所示.

 

 1      /**
 2     * Move 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 amount of pixels to scroll by horizontally<pre name="code" class="java"> /**
 6     * Set the scrolled position of your view. This will cause a call to
 7     * {@link #onScrollChanged(int, int, int, int)} and the view will be
 8     * invalidated.
 9     * @param x the x position to scroll to
10     * @param y the y position to scroll to
11     */
12     public void scrollTo(int x, int y) {
13     if (mScrollX != x || mScrollY != y) {
14     int oldX = mScrollX;
15     int oldY = mScrollY;
16     mScrollX = x;
17     mScrollY = y;
18     invalidateParentCaches();
19     onScrollChanged(mScrollX, mScrollY, oldX, oldY);
20     if (!awakenScrollBars()) {
21     postInvalidateOnAnimation();
22     }
23     }
24     }
1     /* @param y the amount of pixels to scroll by vertically */ 
2     public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }

可见,mScrollX和mScrollY是View类中专门用于记录滑动位置的变量。这两个函数最终调用onScrollChanged()函数,感兴趣者可以参考他们的源代码。

理解了scrollTo(int x, int y)和scrollBy(int x, int y)的用法,就不难理解getScrollX() 和getScrollY()。这两个函数的源码如下所示:

 

 1      /**
 2     * Return the scrolled left position of this view. This is the left edge of
 3     * the displayed part of your view. You do not need to draw any pixels
 4     * farther left, since those are outside of the frame of your view on
 5     * screen.
 6     *
 7     * @return The left edge of the displayed part of your view, in pixels.
 8     */
 9     public final int getScrollX() {
10     return mScrollX;
11     }
12 
13     /**
14     * Return the scrolled top position of this view. This is the top edge of
15     * the displayed part of your view. You do not need to draw any pixels above
16     * it, since those are outside of the frame of your view on screen.
17     *
18     * @return The top edge of the displayed part of your view, in pixels.
19     */
20     public final int getScrollY() {
21     return mScrollY;
22     }

 

 

 

1、之前有朋友反馈button点击不能移动,这是因为android 4.0默认打开了硬件加速,如果想让button移动,请在AndroidManifest的Application标签或者activity标签中加 入:android:hardwareAccelerated="false"

//执行beginScroll() 方法,第一个子View将会滑动到制定位置
//若用手指在MyViewGroup上滑动,子View根据手指滑动的距离来滑动相应的距离
//以为scrollTo , scrollBy 移动的是子View,所以在第一个子View上面包裹了一个
//父View Linearlayout,以便执行scrollTo 的时候不是将第一个子view里面的内容移动,而是
//将第一个子View进行移动
public class MyViewGroup extends LinearLayout {

    private boolean s1 = true;
    Scroller scroller = null;
    private int downX, downY;
    private View oneView;

    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        scroller = new Scroller(context);
        setOrientation(LinearLayout.VERTICAL);
    }

    /**
     * onTouch , invalidate(), postInvalidate()都会触发computeScroll()方法
     * Scroller的startScroll要执行出动画效果是要依靠computeScroll()方法的,
     * 在该方法中调用Scroller.computeScrollOffset()来判断Scroller滑动完成没有
     * 若没有滑动完成继续执行computeScroll(),在computeScroll()执行postInvalidate()
     * 来循环调用computeScroll(),就形成了滑动动画效果了。
     * 说白了Scroller只是记录了滑动信息,并不是它执行了滑动,正真的滑动效果是在computeScroll()执行的
     * ,通过scroller.getCurrX()来获取当前滑动的值,用这个值来进行自己想达到的效果
     */
    @Override
    public void computeScroll() {

        if (scroller.computeScrollOffset()) {
            oneView.scrollTo(scroller.getCurrX(), 0);
            postInvalidate();
        }

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        oneView = getChildAt(0);
    }

    public void beginScroll() {
        if (!s1) {
            scroller.startScroll(0, 0, 0, 0, 1000);
            s1 = true;
        } else {
            scroller.startScroll(0, 0, -500, 0, 1000);
            s1 = false;
        }
        postInvalidate();

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            System.out.println("MotionEvent.ACTION_DOWN");
            requestDisallowInterceptTouchEvent(true);
            downX = (int) event.getX();
            downY = (int) event.getY();
            return true;
        case MotionEvent.ACTION_MOVE:
            System.out.println("MotionEvent.ACTION_MOVE");
            int detalX = (int) (event.getY() - downY);
            oneView.scrollTo(detalX, 0);
            break;
        case MotionEvent.ACTION_UP:
            System.out.println("当会不会执行");
            break;
        }

        // 将事件交给自定义的MyViewGroup处理 ,
        // 如果返回false或者super.onTouch(event)则直接返回到顶端View去了
        // 自定义的MyViewGroup的后续事件也就不会执行了.
        return super.onTouchEvent(event);
    }
}

posted @ 2014-07-02 20:34  perfect亮  阅读(751)  评论(0编辑  收藏  举报