View的滚动原理简单解析

本文详细解析了Android中View的滚动机制,包括scrollTo和scrollBy的区别,以及如何利用这两种方法实现自定义View的滚动效果,例如跑马灯效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一直对View的滚动了解的不深,说明白了吧也能说出个所以然来,所以我就花了点时间做了一个小小的总结,言归正传,view的滑动分为以下三种:

1)View本身不滚动,指滚动View的内容,这也是View类提供的原始方法,通过scrollTo和ScrollBy方法来实现。

2)使用动画,让View来产生滚动效果

3)通过动态的修改LayoutParams的margin等属性让View来产生滚动

本篇博客就简单的分析一下第一种情况,同时本文最后还会简单的提供了一个例子:

View本身就提供了scrollBy和scrollTo方法,其中scrollBy方法又是调用了scrollTo方法:

 

 
  1. public void scrollTo(int x, int y) {

  2. if (mScrollX != x || mScrollY != y) {

  3. int oldX = mScrollX;

  4. int oldY = mScrollY;

  5. //记录滚动的位置

  6.  mScrollX = x;

  7. mScrollY = y;

  8. invalidateParentCaches();

  9. onScrollChanged(mScrollX, mScrollY, oldX, oldY);

  10. if (!awakenScrollBars()) {

  11. postInvalidateOnAnimation();

  12. }

  13. }

  14. }

  15.  
  16. public void scrollBy(int x, int y) {

  17. scrollTo(mScrollX + x, mScrollY + y);

  18. }

从方法的实现上就可以看出来两者的区别,scrollTo用mScrollX和mScrollY记录了滚动的偏移量,没有滚动的时候mScrollX和mScrollY皆为0,它表明了View的内容在x或者y的方向上滚动动的绝对滚动;而scrollBy在mScrollX或者mScrollY的基础上实现了相对滚动

 

在深入研究之前先说说为什么scrollTo和scrollBy滚动的是View的内容而非View本身。因为View在ViewGroup中的位置是由LayoutParams的margin等参数决定的,要想滚动View或者说要想改变View的位置只需要改变LayoutParams的相关参数就可以。但是scrollTo和scrollBy改变的只是mScrollX和mScrollY的值,这两个值对于改变View在ViewGroup里面的位置是毫无关系的;这就排除了scrollTo或者scrollBy滚动的是View本身了,但是又有什么证据证明这两个方法滚动的是View的内容呢?且看这两个变量用在了什么地方就知道了。我们在View的draw方法里面发现了些许痕迹:

 

 
  1. if (background != null) {

  2. final int scrollX = mScrollX;

  3. final int scrollY = mScrollY;

  4.  
  5. 省略了部分代码

  6. if ((scrollX | scrollY) == 0) {

  7. background.draw(canvas);

  8. } else {

  9. canvas.translate(scrollX, scrollY);

  10. background.draw(canvas);

  11. canvas.translate(-scrollX, -scrollY);

  12. }

  13. }


我们知道一个View的显示需要经过测量Measure-->布局Layout-->绘制内容onDraw三个流程的,最后一个流程就是绘制View内容,在哪儿绘制?当然是在画布Canvas上面绘制!上面的代码可以看出mScrollX和mScrollY这两个变量正式交给显示View内容的Canvase来操作的!所以我们说scrollTo或者scrollBy滚动的是View的内容,而不是改变View在parentView显示的位置关系!当然还需要注意的是如果是ViewGroup或者说parentView自己调动了scrollTo/scrollBy方法,那么viewGroup里面的childView的位置是可以改变的,因为childView本身就是ViewGroup里面的内容,(其实ScrollView的滚动原理也是如此),这也是左右滑动屏幕实现的基本思路(可参考网上的这篇博客)。

 

其实关于mScrollX和mSrcorllY的注释就说明了这个问题:The offset, in pixels, by which the content of this view is scrolled!!!!

也就是说mScrollX/xScrollY是相对于“起始位置”在水平/竖直方向的偏移量;结合网上的一些博客资料可以得到如下图的关系

也就是说如果向左滚动,mScrollX为正值,反之为负值;如果向上滚动,mScrollY为正值,反之为负值!

虽然我们可以从源码上看出scrollTo和scrollBy的区别,下面简单的通过例子来直观说一下二者的区别。下面是滚动之前的页面:


上下两个按钮点击事件的代码为:

 

 
  1. /**记录上面按钮的点击次数**/

  2. private int toCount = 0;

  3. public void scrollToTest(View v) {

  4. v.scrollTo(300, 0);

  5. toCount++;

  6. toTV.setText("" + toCount);

  7. }

  8.  
  9. /**记录羡慕按钮的点击次数**/

  10. private int byCount = 0;

  11. public void scrollByTest(View v) {

  12. v.scrollBy(100, 0);

  13. byCount ++;

  14. byTV.setText(""+byCount);

  15. }

也即是说上面的按钮每次点击时向左滚动300的距离,而下面的按钮每次点击向左滚动100的距离.运行效果如下:

 

运行效果发现调用scrollTo的时候无论点击了多少次都不会滚动了,只是在点击第一次的时候才滚动了300.而scrollBy每次滚动在原来的基础上滚动了100,点击三次后达到了与scrollTo一样的效果。所以说scrollTo是一次性滚动到位的绝对滚动,而scrollBy是在前次滚动的基数上继续滚动,逐次滚动到位,是相对滚动!

当然调用scrollBy可以在前面滚动的基础上持续滚动下去,比如上面的那个例子继续点击下面的按钮,仍然会继续滚动。

写到这儿,突然想起了去年刚接触android的时候有一个关于TextView滚动次数的需求,让TextView跑马灯两次就停止滚动,当时水平有限,没有实现出来,现在倒是可以用srcollTo/scrollBy简单粗糙实现出来了。下面就说说这个控制滚动次数的自定义TextView的滚动实现效果如图:

                                                                         跑马灯效果图

上图为实现滚动的效果,切且滚动的位置用图A,图B...图G来表明方便下文的描述。,具体运行效果可以文章最后的demo.下面具体先说说实现的核心原理:

1)获取TextView的字符串的宽度:

 

 
  1. /**TextView字符串的宽度**/

  2. private int textWidth;

  3. private void measureTextWidth() {

  4. Paint paint = this.getPaint();

  5. String str = this.getText().toString();

  6. textWidth = (int) paint.measureText(str);

  7. }

  8.  
  9. @Override

  10.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

  11.         // TODO Auto-generated method stub

  12.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);

  13.         measureTextWidth();

  14.     }


2)让自定义的TextView实现Runnable接口,在run方法里完成滚动的控制,同时提供startScroll方法来开启滚动操作。

 

 

 
  1. private int marqueeTemp =0;

  2.     @Override

  3.     public void run() {

  4.         //已经完成了指定的跑马灯次数

  5.         if(marqueeTemp==marqueeTime) {

  6.             return;

  7.         }

  8.         scrollBy(1,0); //实现跑马灯

  9.         //注意是text字符串的宽度,而不是textView的宽度

  10.         if (getScrollX() >= textWidth) {//如果一轮滚动结束

  11.             marqueeTemp++;//累加滚动次数

  12.  
  13.             //scrollBy实现方式

  14. //            if(marqueeTemp==marqueeTime) {

  15. //                scrollBy(-textWidth,0);

  16. //            }else {

  17. //                scrollBy(-this.getWidth()-textWidth,0);

  18. //            }

  19.             //scrollTo实现方式

  20.             if(marqueeTemp==marqueeTime) {

  21.                 scrollTo(0,0);

  22.             }else {

  23.                 scrollTo(-this.getWidth(),0);

  24.             }

  25.         

  26.         }

  27. //开启下次滚动

  28.         postDelayed(this, 10);

上面的代码完成了滚动的主要工作,请对比这上面的效果图来看。在滚动的时候也就是图B或者图E两个过程时候上面的代码调用了scrollBy(1,0)来逐步滚动1,直到滚动到图C或者图F的时候说明完成了一次滚动,也即是代码中if(getScrollX() >= textWidth)条件成立。此时滚动次数+1.。在完成一次滚动的时候,第二次滚动我们需要让TextView的字符串在图D所在的位置滚动,所以我们需要先让TextView的内容向右滚动到D的位置,然后在向左继续跑马灯,通过对scrollTo和ScrollBy的了解,这两种方式都可以让处于图C状态的内容滚动到图D状态位置:

 

前提正如前面所说向左移动的时候mScrollX>0.向右mScrollX<0;

1)scrollBy的实现方式:因为scrollBy是逐步滚动。对比着图C和图D,我们只需要让scollBy的向右移动字符串的宽度+TextView的宽度即可,即上面代码中的scrollBy(-this.getWidth()-textWidth,0);

2)scollTo的实现方式:一步到位的实现方式,因为从源码上看scrollBy调用了scrollTo(mScroll+x,0),而且完成一次滚动后mScroll=textWidth(字符串的宽度);所以我们直接调用scollTo(-this.getWidth(),0)就可以让TextView的内容滚动到图D位置。

通过1和2的讲解,我们更是可以进一步的理解scrollBy和scrollTo的不同之处!!!!

另外当滚动指定次数过后需要让TextView的内容回到初始位置,即上图中的图G位置,同样可以用srcollBy和scrollTo两种方式:

1)scrollBy方式:if(marqueeTemp==marqueeTime) {//完成了指定次数的滚动
              scrollBy(-textWidth,0);
           }

2)scrollTo方式:if(marqueeTemp==marqueeTime) {
                scrollTo(0,0);
            }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值