Scroller原理

来源:互联网 发布:淘宝客服是干嘛的? 编辑:程序博客网 时间:2024/06/10 15:49
  1. package cc.ww;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Canvas;  
  5. import android.util.AttributeSet;  
  6. import android.view.MotionEvent;  
  7. import android.view.View;  
  8. import android.view.ViewGroup;  
  9. import android.widget.Scroller;  
  10. import android.widget.Toast;  
  11. /** 
  12.  * Scroller原理: 
  13.  * 为了让View或者ViewGroup的内容发生移动,我们常用scrollTo()和scrollBy()方法. 
  14.  * 但这两个方法执行的速度都很快,瞬间完成了移动感觉比较生硬. 
  15.  * 为了使View或者ViewGroup的内容发生移动时比较平滑或者有其他的移动渐变效果 
  16.  * 可采用Scroller来实现. 
  17.  * 在具体实现时,我们继承并重写View或者ViewGroup时可生成一个Scroller由它来具体 
  18.  * 掌控移动过程和结合插值器Interpolator调用scrollTo()和scrollBy()方法. 
  19.  *  
  20.  *  
  21.  * Scroller的两个主要构造方法: 
  22.  * 1 public Scroller(Context context) {} 
  23.  * 2 public Scroller(Context context, Interpolator interpolator){} 
  24.  * 采用第一个构造方法时,在移动中会采用一个默认的插值器Interpolator 
  25.  * 也可采用第二个构造方法,为移动过程指定一个插值器Interpolator 
  26.  *  
  27.  *  
  28.  * Scroller的调用过程以及View的重绘: 
  29.  * 1 调用public void startScroll(int startX, int startY, int dx, int dy) 
  30.  *   该方法为scroll做一些准备工作. 
  31.  *   比如设置了移动的起始坐标,滑动的距离和方向以及持续时间等. 
  32.  *   该方法并不是真正的滑动scroll的开始,感觉叫prepareScroll()更贴切些. 
  33.  *    
  34.  * 2 调用invalidate()或者postInvalidate()使View(ViewGroup)树重绘 
  35.  *   重绘会调用View的draw()方法 
  36.  *   draw()一共有六步:  
  37.  *   Draw traversal performs several drawing steps which must be executed    
  38.  *   in the appropriate order:    
  39.  *   1. Draw the background    
  40.  *   2. If necessary, save the canvas' layers to prepare for fading    
  41.  *   3. Draw view's content    
  42.  *   4. Draw children    
  43.  *   5. If necessary, draw the fading edges and restore layers    
  44.  *   6. Draw decorations (scrollbars for instance) 
  45.  *   其中最重要的是第三步和第四步 
  46.  *   第三步会去调用onDraw()绘制内容 
  47.  *   第四步会去调用dispatchDraw()绘制子View 
  48.  *   重绘分两种情况: 
  49.  *   2.1 ViewGroup的重绘 
  50.  *       在完成第三步onDraw()以后,进入第四步ViewGroup重写了 
  51.  *       父类View的dispatchDraw()绘制子View,于是这样继续调用: 
  52.  *       dispatchDraw()-->drawChild()-->child.computeScroll(); 
  53.  *   2.2 View的重绘 
  54.  *       我们注意到在2提到的"调用invalidate()".那么对于View它又是怎么 
  55.  *       调用到了computeScroll()呢?View没有子View的.所以在View的源码里可以 
  56.  *       看到dispatchDraw()是一个空方法.所以它的调用路径和ViewGroup是不一样的. 
  57.  *       在此不禁要问:如果一个ButtonSubClass extends Button 当mButtonSubClass 
  58.  *       执行mButtonSubClass.scrollTo()方法时怎么触发了ButtonSubClass类中重写 
  59.  *       的computeScroll()方法??? 
  60.  *       在这里我也比较疑惑,只有借助网上的资料和源码去从invalidate()看起. 
  61.  *       总的来说是这样的:当View调用invalidate()方法时,会导致整个View树进行 
  62.  *       从上至下的一次重绘.比如从最外层的Layout到里层的Layout,直到每个子View. 
  63.  *       在重绘View树时ViewGroup和View时按理都会经过onMeasure()和onLayout()以及 
  64.  *       onDraw()方法.当然系统会判断这三个方法是否都必须执行,如果没有必要就不会调用. 
  65.  *       看到这里就明白了:当这个子View的父容器重绘时,也会调用上面提到的线路: 
  66.  *       onDraw()-->dispatchDraw()-->drawChild()-->child.computeScroll(); 
  67.  *       于是子View(比如此处举例的ButtonSubClass类)中重写的computeScroll()方法 
  68.  *       就会被调用到. 
  69.  *        
  70.  * 3 View树的重绘会调用到View中的computeScroll()方法 
  71.  *  
  72.  * 4 在computeScroll()方法中 
  73.  *   在View的源码中可以看到public void computeScroll(){}是一个空方法. 
  74.  *   具体的实现需要自己来写.在该方法中我们可调用scrollTo()或scrollBy() 
  75.  *   来实现移动.该方法才是实现移动的核心. 
  76.  *   4.1 利用Scroller的mScroller.computeScrollOffset()判断移动过程是否完成 
  77.  *       注意:该方法是Scroller中的方法而不是View中的!!!!!! 
  78.  *       public boolean computeScrollOffset(){ } 
  79.  *       Call this when you want to know the new location. 
  80.  *       If it returns true,the animation is not yet finished.   
  81.  *       loc will be altered to provide the new location. 
  82.  *       返回true时表示还移动还没有完成. 
  83.  *   4.2 若动画没有结束,则调用:scrollTo(By)(); 
  84.  *       使其滑动scrolling 
  85.  *        
  86.  * 5 再次调用invalidate(). 
  87.  *   调用invalidate()方法那么又会重绘View树. 
  88.  *   从而跳转到第3步,如此循环,直到computeScrollOffset返回false 
  89.  *        
  90.  *    
  91.  *    
  92.  *   具体的滑动过程,请参见示图 
  93.  *    
  94.  *    
  95.  *  
  96.  *    
  97.  *    
  98.  * 通俗的理解: 
  99.  * 从上可见Scroller执行流程里面的三个核心方法 
  100.  * mScroller.startScroll() 
  101.  * mScroller.computeScrollOffset() 
  102.  * view.computeScroll() 
  103.  * 1 在mScroller.startScroll()中为滑动做了一些初始化准备. 
  104.  *   比如:起始坐标,滑动的距离和方向以及持续时间(有默认值)等. 
  105.  *   其实除了这些,在该方法内还做了些其他事情: 
  106.  *   比较重要的一点是设置了动画开始时间. 
  107.  *  
  108.  * 2 computeScrollOffset()方法主要是根据当前已经消逝的时间 
  109.  *   来计算当前的坐标点并且保存在mCurrX和mCurrY值中. 
  110.  *   因为在mScroller.startScroll()中设置了动画时间,那么 
  111.  *   在computeScrollOffset()方法中依据已经消逝的时间就很容易 
  112.  *   得到当前时刻应该所处的位置并将其保存在变量mCurrX和mCurrY中. 
  113.  *   除此之外该方法还可判断动画是否已经结束. 
  114.  *    
  115.  *   所以在该示例中: 
  116.  *   @Override 
  117.  *   public void computeScroll() { 
  118.  *      super.computeScroll(); 
  119.  *      if (mScroller.computeScrollOffset()) { 
  120.  *          scrollTo(mScroller.getCurrX(), 0); 
  121.  *          invalidate(); 
  122.  *      } 
  123.  *   } 
  124.  *   先执行mScroller.computeScrollOffset()判断了滑动是否结束 
  125.  *   2.1 返回false,滑动已经结束. 
  126.  *   2.2 返回true,滑动还没有结束. 
  127.  *       并且在该方法内部也计算了最新的坐标值mCurrX和mCurrY. 
  128.  *       就是说在当前时刻应该滑动到哪里了. 
  129.  *       既然computeScrollOffset()如此贴心,盛情难却啊! 
  130.  *       于是我们就覆写View的computeScroll()方法, 
  131.  *       调用scrollTo(By)滑动到那里!满足它的一番苦心吧. 
  132.  *    
  133.  *  
  134.  * 备注说明: 
  135.  * 1 示例没有做边界判断和一些优化,在这方面有bug. 
  136.  *   重点是学习Scroller的流程 
  137.  * 2 不用纠结getCurrX()与getScrollX()有什么差别,二者得到的值一样. 
  138.  *   但要注意它们是属于不同类里的. 
  139.  *   getCurrX()-------> Scroller.getCurrX() 
  140.  *   getScrollX()-----> View.getScrollX() 
  141.  *  
  142.  *  
  143.  * 参考资料: 
  144.  * 0 http://androidxref.com/2.3.6/xref 
  145.  * 1 http://blog.csdn.net/wangjinyu501/article/details/32339379 
  146.  * 2 http://blog.csdn.net/zjmdp/article/details/7713209 
  147.  * 3 http://blog.csdn.net/xiaanming/article/details/17483273 
  148.  *   Thank you very much 
  149.  * 
  150.  */  
  151. public class ScrollLauncherViewGroup extends ViewGroup {  
  152.     private int lastX;  
  153.     private int currentX;  
  154.     private int distanceX;  
  155.     private Context mContext;  
  156.     private Scroller mScroller;  
  157.     public ScrollLauncherViewGroup(Context context) {  
  158.         super(context);  
  159.         mContext=context;  
  160.         mScroller=new Scroller(context);  
  161.     }  
  162.   
  163.     public ScrollLauncherViewGroup(Context context, AttributeSet attrs) {  
  164.         super(context, attrs);  
  165.     }  
  166.   
  167.     public ScrollLauncherViewGroup(Context context, AttributeSet attrs,int defStyle) {  
  168.         super(context, attrs, defStyle);  
  169.     }  
  170.       
  171.     @Override  
  172.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  173.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  174.           
  175.     }  
  176.   
  177.       
  178.     /** 
  179.      * 注意: 
  180.      * 1 getWidth()和getHeight()得到是屏幕的宽和高 
  181.      *   因为在布局时指定了该控件的宽和高为fill_parent 
  182.      * 2 view.getScrollX(Y)()得打mScrollX(Y) 
  183.      * 3 调用scrollTo(x, y)后,x和y分别被赋值给mScrollX和mScrollY 
  184.      *   请注意坐标方向. 
  185.      */  
  186.     @Override  
  187.     protected void onLayout(boolean arg0, int l, int t, int r, int b) {  
  188.          for (int i = 0; i < getChildCount(); i++) {    
  189.              View childView = getChildAt(i);    
  190.              childView.layout(i*getWidth(), 0,getWidth()+ i*getWidth(),getHeight());    
  191.          }    
  192.     }  
  193.       
  194.     @Override  
  195.     protected void onDraw(Canvas canvas) {  
  196.         super.onDraw(canvas);  
  197.     }  
  198.       
  199.     @Override  
  200.     public boolean onTouchEvent(MotionEvent event) {  
  201.         switch (event.getAction()) {  
  202.         case MotionEvent.ACTION_DOWN:  
  203.             lastX=(int) event.getX();  
  204.             break;  
  205.         case MotionEvent.ACTION_MOVE:  
  206.             currentX=(int) event.getX();  
  207.             distanceX=currentX-lastX;  
  208.             mScroller.startScroll(getScrollX(), 0, -distanceX, 0);  
  209.             break;  
  210.         case MotionEvent.ACTION_UP:  
  211.             //手指从屏幕右边往左滑动,手指抬起时滑动到下一屏  
  212.             if (distanceX<0&&Math.abs(distanceX)>50) {  
  213.                 mScroller.startScroll(getScrollX(), 0, getWidth()-(getScrollX()%getWidth()), 0);  
  214.             //手指从屏幕左边往右滑动,手指抬起时滑动到上一屏  
  215.             } else if (distanceX>0&&Math.abs(distanceX)>50) {  
  216.                 mScroller.startScroll(getScrollX(), 0, -(getScrollX()%getWidth()), 0);  
  217.             }  
  218.             break;  
  219.               
  220.         default:  
  221.             break;  
  222.         }  
  223.         //重绘View树  
  224.         invalidate();   
  225.         return true;  
  226.     }  
  227.       
  228.     @Override  
  229.     public void computeScroll() {  
  230.         super.computeScroll();  
  231.         if (mScroller.computeScrollOffset()) {  
  232.             scrollTo(mScroller.getCurrX(), 0);  
  233.             invalidate();  
  234.         }else{  
  235.             if (mScroller.getCurrX()==getWidth()*(getChildCount()-1)) {  
  236.                 Toast.makeText(mContext, "已滑动到最后一屏", Toast.LENGTH_SHORT).show();  
  237.             }  
  238.         }  
  239.     }  
  240.   
  241. }  



0 0
原创粉丝点击