尊重原创作者,转载请注明出处:


  Scroller这个类理解起来有一定的困难,刚开始接触Scroller类的程序员可能无法理解Scroller和View系统是怎么样联系起来的。我经过自己的学习和实践,对Scroller的用法和工作原理有了一定的理解,在这里和大家分享一下,希望大家多多指教。

      首先从源码开始分析:

View.java

 


    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
    8. {  
    9. }



     

        computeScroll是一个空函数,很明显我们需要去实现它,至于做什么,就由我们自己来决定了。

        因为View的子类很多,在下面的例子中,我会在一个自定义的类MyLinearLayout中去实现它。


        ViewGroup.java

       


    1. @Override
    2. protected void
    3.   
    4.             .......  
    5.   
    6.             .......  
    7.   
    8.             .......  
    9.   
    10.             .......  
    11.   
    12. for (int i = 0; i < count; i++) {  
    13. final
    14. if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)  
    15.   
    16.             {  
    17.                 more |= drawChild(canvas, child, drawingTime);  
    18.             }  
    19.   
    20.             .......  
    21.   
    22.             .......  
    23.   
    24.             .......


    从dispatchDraw函数可以看出,ViewGroup会对它的每个孩子调用drawChild(),  在下面的例子中, ContentLinearLayout的孩子有2个,是2个MyLinearLayout类型的实例。

    再看看drawChild函数:


    1. protected boolean drawChild(Canvas canvas, View child, long
    2.   
    3.             ................  
    4.   
    5.             ................  
    6.   
    7.            child.computeScroll();  
    8.   
    9.             ................  
    10.   
    11.             ................  
    12.   
    13. }

     

    看到这里,我想大家应该就明白了,在父容器重画自己的孩子时,它会调用孩子的computScroll方法,也就是说例程中的ContentLinearLayout在调用dispatchDraw()函数时会调用MyLinearLayout的computeScroll方法。

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

        看到这里大家一定迫不及待的想看代码了,代码如下:

        

    1. package
    2.     
    3. import
    4. import
    5. import
    6. import
    7. import
    8. import
    9. import
    10. import
    11. import
    12. import
    13. import
    14.     
    15. public class MainActivity extends
    16. private static final String TAG = "TestScrollerActivity";    
    17.     LinearLayout lay1,lay2,lay0;    
    18. private
    19. @Override
    20. public void
    21. super.onCreate(savedInstanceState);    
    22. new Scroller(this, new DecelerateInterpolator(2.0F));  
    23. new MyLinearLayout(this);    
    24. new MyLinearLayout(this);    
    25.      
    26. this.getResources().getColor(android.R.color.darker_gray));    
    27. this.getResources().getColor(android.R.color.white));    
    28. new ContentLinearLayout(this);    
    29.         lay0.setOrientation(LinearLayout.VERTICAL);    
    30. new
    31.         (LinearLayout.LayoutParams.FILL_PARENT,LinearLayout.LayoutParams.FILL_PARENT);        
    32. this.setContentView(lay0, p0);    
    33.      
    34. new
    35.         (LinearLayout.LayoutParams.FILL_PARENT,LinearLayout.LayoutParams.FILL_PARENT);        
    36. 1;    
    37.         lay0.addView(lay1,p1);    
    38. new
    39.         (LinearLayout.LayoutParams.FILL_PARENT,LinearLayout.LayoutParams.FILL_PARENT);        
    40. 1;    
    41.         lay0.addView(lay2,p2);    
    42. new MyButton(this);    
    43. new MyButton(this);    
    44. "btn in layout1");    
    45. "btn in layout2");    
    46. new
    47. @Override
    48. public void
    49. 0, 0, -200, 0, 500);    
    50.                 }    
    51.         });    
    52. new
    53. @Override
    54. public void
    55. 0, 0, -300, 0, 600);    
    56.                 }    
    57.         });    
    58.         lay1.addView(btn1);    
    59.         lay2.addView(btn2);    
    60. null);  
    61. null);  
    62. null);  
    63.     }    
    64. class MyButton extends
    65.     {    
    66. public
    67.      {    
    68. super(ctx);    
    69.      }    
    70. @Override
    71. protected void
    72.      {    
    73. super.onDraw(canvas);    
    74. "MyButton", this.toString() + " onDraw------");    
    75.      }    
    76.     }    
    77.         
    78. class MyLinearLayout extends
    79.     {    
    80. public
    81.      {  
    82. super(ctx);    
    83.      }    
    84.          
    85. @Override
    86. /** 
    87.          * Called by a parent to request that a child update its values for mScrollX 
    88.          * and mScrollY if necessary. This will typically be done if the child is 
    89.          * animating a scroll using a {@link android.widget.Scroller Scroller} 
    90.          * object. 
    91.          */
    92. public void
    93.      {      
    94. this.toString() + " computeScroll-----------");    
    95. if (mScroller.computeScrollOffset())//如果mScroller没有调用startScroll,这里将会返回false。  
    96.     {      
    97. //因为调用computeScroll函数的是MyLinearLayout实例,  
    98. //所以调用scrollTo移动的将是该实例的孩子,也就是MyButton实例  
    99. 0);  
    100. "getCurrX = "
    101.     
    102. //继续让系统重绘  
    103. //postInvalidate();
    104.     }    
    105.      }    
    106.     }    
    107.         
    108. class ContentLinearLayout extends
    109.     {    
    110. public
    111.      {    
    112. super(ctx);    
    113.      }    
    114.          
    115. @Override
    116. protected void
    117.      {    
    118. "ContentLinearLayout", "contentview dispatchDraw");    
    119. super.dispatchDraw(canvas);    
    120.      }    
    121.     }    
    122. }


        对代码做一个简单介绍:

        例子中定义了2个MyButton实例btn1和btn2,它们将被其父容器MyLinearLayout实例lay1和lay2通过调用scrollTo来移动。

      ContentLinearLayout实例lay0为Activity的contentview,它有2个孩子,分别是lay1和lay2。

       mScroller是一个封装位置和速度等信息的变量,startScroll()函数只是对它的一些成员变量做一些设置,这个设置的唯一效果就是导致mScroller.computeScrollOffset()    返回true。

          这里大家可能有个疑问,既然startScroll()只是虚晃一枪,那scroll的动态效果到底是谁触发的呢?

    后面我将给出答案。


    运行程序,我们来看看Log

    点击btn1:


    点击btn2:

       对照Log,我从button被点击开始,对整个绘制流程进行分析,首先button被点击(这里将回答上文的问题),button的背景将发生变化,这时button将调用invalidate()请求重绘,这就是View系统重绘的源头,即scroll动态效果的触发者。与此同时,mScroller.startScroll被调用了,mScroller在此时被设置了一些有效值。

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

       好了,现在整个流程都分析完了,相信大家应该清楚了Scroller类与View系统的关系了吧。理解了Scroller的工作原理,你会发现原来Scroller类并不神秘,甚至有点被动,它除了储存一些数值,什么其他的事情都没做,Scroller类中的一些变量mStartX, mFinalX, mDuration等等的意义也很好理解。