http://blog.csdn.net/wangjinyu501/article/details/32339379

http://blog.csdn.net/wangjinyu501/article/details/32339379

http://blog.csdn.net/wangjinyu501/article/details/32339379

http://blog.csdn.net/wangjinyu501/article/details/32339379


Android scrollTo() scrollBy() Scroller讲解及应用

分类:Android 817人阅读 评论(6) 收藏 举报 版本:1.0 日期:2014.6.17 2014.6.18 版权:© 2014 kince 转载注明出处
scrollTo() 、scrollBy()及 Scroller在视图滑动中经常使用到,比如最常见的Launcher就是用这种方式实现。为了更加明了的理解,还是去看一下源码。在View类中,scrollTo的代码如下: [html] view plain copy
  1. /**
  2. *Setthescrolledpositionofyourview.Thiswillcauseacallto
  3. *{@link#onScrollChanged(int,int,int,int)}andtheviewwillbe
  4. *invalidated.
  5. *@paramxthexpositiontoscrollto
  6. *@paramytheypositiontoscrollto
  7. */
  8. publicvoidscrollTo(intx,inty){
  9. if(mScrollX!=x||mScrollY!=y){
  10. intoldX=mScrollX;
  11. intoldY=mScrollY;
  12. mScrollX=x;
  13. mScrollY=y;
  14. invalidateParentCaches();
  15. onScrollChanged(mScrollX,mScrollY,oldX,oldY);
  16. if(!awakenScrollBars()){
  17. postInvalidateOnAnimation();
  18. }
  19. }
  20. }
在注释中说到,该方法用于设置滚动视图的位置,然后会调用onScrollChanged(int, int, int, int)方法,最后视图会被刷新。那它是如何让视图滚动的呢?首先注意到在这个方法中有两个变量:mScrollX、mScrollY。这两个变量是在View类中定义的, [html] view plain copy
  1. /**
  2. *Theoffset,inpixels,bywhichthecontentofthisviewisscrolled
  3. *horizontally.
  4. *{@hide}
  5. */
  6. @ViewDebug.ExportedProperty(category="scrolling")
  7. protectedintmScrollX;
  8. /**
  9. *Theoffset,inpixels,bywhichthecontentofthisviewisscrolled
  10. *vertically.
  11. *{@hide}
  12. */
  13. @ViewDebug.ExportedProperty(category="scrolling")
  14. protectedintmScrollY;
这两个变量分别是视图在水平和垂直方向的偏移量,
  • mScrollX:该视图内容相当于视图起始坐标的偏移量, X轴方向
  • mScrollY:该视图内容相当于视图起始坐标的偏移量, Y轴方向
分别通过getScrollX() 和getScrollY()方法获得。 我们知道Android的坐标体系是这样的: (ps:相对于父类视图的左上角坐标为坐标原点(0,0),而不是整体ViewGroup的左上角为原点。) scrollTo()方法就是将一个视图移动到指定位置,偏移量mScrollX、mScrollY就是视图初始位置的距离,默认是情况下当然是0。如果视图要发生移动,比如要移动到(x,y),首先要检查这个点的坐标是否和偏移量一样,因为scrollTo()是移动到指定的点,如果这次移动的点的坐标和上次偏移量一样,也就是说这次移动和上次移动的坐标是同一个,那么就没有必要进行移动了。这也是这个方法为什么进行if (mScrollX != x || mScrollY != y) {这样一个判断的原因。接下来再看一下scrollBy()的源码, [html] view plain copy
  1. /**
  2. *Movethescrolledpositionofyourview.Thiswillcauseacallto
  3. *{@link#onScrollChanged(int,int,int,int)}andtheviewwillbe
  4. *invalidated.
  5. *@paramxtheamountofpixelstoscrollbyhorizontally
  6. *@paramytheamountofpixelstoscrollbyvertically
  7. */
  8. publicvoidscrollBy(intx,inty){
  9. scrollTo(mScrollX+x,mScrollY+y);
  10. }
很简单,就是直接调用了scrollTo方法,但是从这个方法的实现机制可以看出,它是一个累加减的过程,不断的将当前视图内容继续偏移(x , y)个单位。比如第一次scrollBy(10,10),第二次scrollBy(10,10),那么最后的结果就相当于scrollTo(20,20)。 理解这两个方法的实现机制之后,还有一个重要的问题,就是关于移动的方向。比如一个位于原点的视图,如果调用了scrollTo(0,20)方法,如果你认为是垂直向下移动20像素就错了,其实是向上移动了20个像素。在上图中,我已经给出了一个十字坐标,正负代表坐标的正负以及相应的方向。为什么会是这样的情况呢?按坐标系的认知来说,不应该是这个结果的,所以必须研究一下究竟为何。 线索当然还是要分析源码,在scrollTo(x, y)中,x和y分别被赋值给了mScrollX和mScrollY,最后调用了postInvalidateOnAnimation()方法。之后这个方法会通知View进行重绘。所以就去看一下draw()方法的源码,因为这个方法比较长,基于篇幅就不全部列出,直说重点。先列出方法的前几行, [html] view plain copy
  1. publicvoiddraw(Canvascanvas){
  2. if(mClipBounds!=null){
  3. canvas.clipRect(mClipBounds);
  4. }
  5. finalintprivateFlags=mPrivateFlags;
  6. finalbooleandirtyOpaque=(privateFlags&PFLAG_DIRTY_MASK)==PFLAG_DIRTY_OPAQUE&&
  7. (mAttachInfo==null||!mAttachInfo.mIgnoreDirtyState);
  8. mPrivateFlags=(privateFlags&~PFLAG_DIRTY_MASK)|PFLAG_DRAWN;
  9. /*
  10. *Drawtraversalperformsseveraldrawingstepswhichmustbeexecuted
  11. *intheappropriateorder:
  12. *
  13. *1.Drawthebackground
  14. *2.Ifnecessary,savethecanvas'layerstoprepareforfading
  15. *3.Drawview'scontent
  16. *4.Drawchildren
  17. *5.Ifnecessary,drawthefadingedgesandrestorelayers
  18. *6.Drawdecorations(scrollbarsforinstance)
  19. */
  20. //Step1,drawthebackground,ifneeded
  21. intsaveCount;
在注释中可以看到这个方法的步骤,第六步6就是绘制scrollbars,而scrollbars就是由于scroll引起的,所以先定位到这里。在方法的最后,看到了 [html] view plain copy
  1. //Step6,drawdecorations(scrollbars)
  2. onDrawScrollBars(canvas);
然后看一下onDrawScrollBars(canvas)方法,
[html] view plain copy
  1. protectedfinalvoidonDrawScrollBars(Canvascanvas){
  2. //scrollbarsaredrawnonlywhentheanimationisrunning
  3. finalScrollabilityCachecache=mScrollCache;
  4. if(cache!=null){
  5. intstate=cache.state;
  6. if(state==ScrollabilityCache.OFF){
  7. return;
  8. }
  9. booleaninvalidate=false;
  10. if(state==ScrollabilityCache.FADING){
  11. //We'refading--getourfadeinterpolation
  12. if(cache.interpolatorValues==null){
  13. cache.interpolatorValues=newfloat[1];
  14. }
  15. float[]values=cache.interpolatorValues;
  16. //Stopstheanimationifwe'redone
  17. if(cache.scrollBarInterpolator.timeToValues(values)==
  18. Interpolator.Result.FREEZE_END){
  19. cache.state=ScrollabilityCache.OFF;
  20. }else{
  21. cache.scrollBar.setAlpha(Math.round(values[0]));
  22. }
  23. //Thiswillmakethescrollbarsinvalthemselvesafter
  24. //drawing.Weonlywantthiswhenwe'refadingsothat
  25. //wepreventexcessiveredraws
  26. invalidate=true;
  27. }else{
  28. //We'rejuston--butwemayhavebeenfadingbeforeso
  29. //resetalpha
  30. cache.scrollBar.setAlpha(255);
  31. }
  32. finalintviewFlags=mViewFlags;
  33. finalbooleandrawHorizontalScrollBar=
  34. (viewFlags&SCROLLBARS_HORIZONTAL)==SCROLLBARS_HORIZONTAL;
  35. finalbooleandrawVerticalScrollBar=
  36. (viewFlags&SCROLLBARS_VERTICAL)==SCROLLBARS_VERTICAL
  37. &&!isVerticalScrollBarHidden();
  38. if(drawVerticalScrollBar||drawHorizontalScrollBar){
  39. finalintwidth=mRight-mLeft;
  40. finalintheight=mBottom-mTop;
  41. finalScrollBarDrawablescrollBar=cache.scrollBar;
  42. finalintscrollX=mScrollX;
  43. finalintscrollY=mScrollY;
  44. finalintinside=(viewFlags&SCROLLBARS_OUTSIDE_MASK)==0?~0:0;
  45. intleft;
  46. inttop;
  47. intright;
  48. intbottom;
  49. if(drawHorizontalScrollBar){
  50. intsize=scrollBar.getSize(false);
  51. if(size<=0){
  52. size=cache.scrollBarSize;
  53. }
  54. scrollBar.setParameters(computeHorizontalScrollRange(),
  55. computeHorizontalScrollOffset(),
  56. computeHorizontalScrollExtent(),false);
  57. finalintverticalScrollBarGap=drawVerticalScrollBar?
  58. getVerticalScrollbarWidth():0;
  59. top=scrollY+height-size-(mUserPaddingBottom&inside);
  60. left=scrollX+(mPaddingLeft&inside);
  61. right=scrollX+width-(mUserPaddingRight&inside)-verticalScrollBarGap;
  62. bottom=top+size;
  63. onDrawHorizontalScrollBar(canvas,scrollBar,left,top,right,bottom);
  64. if(invalidate){
  65. invalidate(left,top,right,bottom);
  66. }
  67. }
  68. if(drawVerticalScrollBar){
  69. intsize=scrollBar.getSize(true);
  70. if(size<=0){
  71. size=cache.scrollBarSize;
  72. }
  73. scrollBar.setParameters(computeVerticalScrollRange(),
  74. computeVerticalScrollOffset(),
  75. computeVerticalScrollExtent(),true);
  76. intverticalScrollbarPosition=mVerticalScrollbarPosition;
  77. if(verticalScrollbarPosition==SCROLLBAR_POSITION_DEFAULT){
  78. verticalScrollbarPosition=isLayoutRtl()?
  79. SCROLLBAR_POSITION_LEFT:SCROLLBAR_POSITION_RIGHT;
  80. }
  81. switch(verticalScrollbarPosition){
  82. default:
  83. caseSCROLLBAR_POSITION_RIGHT:
  84. left=scrollX+width-size-(mUserPaddingRight&inside);
  85. break;
  86. caseSCROLLBAR_POSITION_LEFT:
  87. left=scrollX+(mUserPaddingLeft&inside);
  88. break;
  89. }
  90. top=scrollY+(mPaddingTop&inside);
  91. right=left+size;
  92. bottom=scrollY+height-(mUserPaddingBottom&inside);
  93. onDrawVerticalScrollBar(canvas,scrollBar,left,top,right,bottom);
  94. if(invalidate){
  95. invalidate(left,top,right,bottom);
  96. }
  97. }
  98. }
  99. }
  100. }
这个方法分别绘制水平和垂直方向的ScrollBar,最后都会调用invalidate(left, top, right, bottom)方法。 [html] view plain copy
  1. publicvoidinvalidate(intl,intt,intr,intb){
  2. if(skipInvalidate()){
  3. return;
  4. }
  5. if((mPrivateFlags&(PFLAG_DRAWN|PFLAG_HAS_BOUNDS))==(PFLAG_DRAWN|PFLAG_HAS_BOUNDS)||
  6. (mPrivateFlags&PFLAG_DRAWING_CACHE_VALID)==PFLAG_DRAWING_CACHE_VALID||
  7. (mPrivateFlags&PFLAG_INVALIDATED)!=PFLAG_INVALIDATED){
  8. mPrivateFlags&=~PFLAG_DRAWING_CACHE_VALID;
  9. mPrivateFlags|=PFLAG_INVALIDATED;
  10. mPrivateFlags|=PFLAG_DIRTY;
  11. finalViewParentp=mParent;
  12. finalAttachInfoai=mAttachInfo;
  13. //noinspectionPointlessBooleanExpression,ConstantConditions
  14. if(!HardwareRenderer.RENDER_DIRTY_REGIONS){
  15. if(p!=null&&ai!=null&&ai.mHardwareAccelerated){
  16. //fast-trackforGL-enabledapplications;justinvalidatethewholehierarchy
  17. //withanulldirtyrect,whichtellstheViewAncestortoredraweverything
  18. p.invalidateChild(this,null);
  19. return;
  20. }
  21. }
  22. if(p!=null&&ai!=null&&l<r&&t<b){
  23. finalintscrollX=mScrollX;
  24. finalintscrollY=mScrollY;
  25. finalRecttmpr=ai.mTmpInvalRect;
  26. tmpr.set(l-scrollX,t-scrollY,r-scrollX,b-scrollY);
  27. p.invalidateChild(this,tmpr);
  28. }
  29. }
  30. }
在这个方法的最后,可以看到tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY),真相终于大白,相信也都清楚为什么会是反方向的了。也会明白当向右移动视图时候,为什么getScrollX()返回值会是负的了。下面做一个测试的demo,来练习一下这两个方法的使用。 Activity:
[html] view plain copy
  1. packagecom.kince.scrolldemo;
  2. importandroid.app.Activity;
  3. importandroid.os.Bundle;
  4. importandroid.view.View;
  5. importandroid.view.View.OnClickListener;
  6. importandroid.widget.Button;
  7. importandroid.widget.TextView;
  8. publicclassMainActivityextendsActivityimplementsOnClickListener{
  9. privateButtonmButton1;
  10. privateButtonmButton2;
  11. privateButtonmButton3;
  12. privateTextViewmTextView;
  13. @Override
  14. protectedvoidonCreate(BundlesavedInstanceState){
  15. super.onCreate(savedInstanceState);
  16. setContentView(R.layout.activity_main);
  17. mTextView=(TextView)this.findViewById(R.id.tv);
  18. mButton1=(Button)this.findViewById(R.id.button_scroll1);
  19. mButton2=(Button)this.findViewById(R.id.button_scroll2);
  20. mButton3=(Button)this.findViewById(R.id.button_scroll3);
  21. mButton1.setOnClickListener(this);
  22. mButton2.setOnClickListener(this);
  23. mButton3.setOnClickListener(this);
  24. }
  25. @Override
  26. publicvoidonClick(Viewv){
  27. //TODOAuto-generatedmethodstub
  28. switch(v.getId()){
  29. caseR.id.button_scroll1:
  30. mTextView.scrollTo(-10,-10);
  31. break;
  32. caseR.id.button_scroll2:
  33. mTextView.scrollBy(-2,-2);
  34. break;
  35. caseR.id.button_scroll3:
  36. mTextView.scrollTo(0,0);
  37. break;
  38. default:
  39. break;
  40. }
  41. }
  42. }
xml: [html] view plain copy
  1. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical">
  6. <RelativeLayout
  7. android:layout_width="match_parent"
  8. android:layout_height="400dp"
  9. android:background="@android:color/holo_green_light">
  10. <TextView
  11. android:id="@+id/tv"
  12. android:layout_width="wrap_content"
  13. android:layout_height="wrap_content"
  14. android:layout_centerInParent="true"
  15. android:background="@android:color/holo_blue_dark"
  16. android:textSize="20sp"
  17. android:text="SCROLL"/>
  18. </RelativeLayout>
  19. <LinearLayout
  20. android:layout_width="match_parent"
  21. android:layout_height="wrap_content"
  22. android:gravity="center_horizontal"
  23. android:orientation="horizontal">
  24. <Button
  25. android:id="@+id/button_scroll1"
  26. android:layout_width="wrap_content"
  27. android:layout_height="wrap_content"
  28. android:text="SCROLL_TO"/>
  29. <Button
  30. android:id="@+id/button_scroll2"
  31. android:layout_width="wrap_content"
  32. android:layout_height="wrap_content"
  33. android:text="SCROLL_BY"/>
  34. <Button
  35. android:id="@+id/button_scroll3"
  36. android:layout_width="wrap_content"
  37. android:layout_height="wrap_content"
  38. android:text="复位"/>
  39. </LinearLayout>
  40. </LinearLayout>
点击SCROLL_TO按钮,TxtView移动后显示如下: 然后,不断按SCROLL_BY按钮,显示如下: 可以看到,TextView逐渐向下移动,直到看不到文字(还会继续移动)。看到这样的结果,可能会与之前预想的有些出入。我之前以为TextView会在它的父类容器控件中移动,也就是图中绿黄色的区域。结果却是视图相对于自身的移动,其实还是对于这个方法包括mScrollX、mScrollY的理解不全面,回过头来再看一下 protected int mScrollX; //The offset, in pixels, by which the content of this view is scrolled 重点就是the content of this view,视图的内容的偏移量,而不是视图相对于其他容器或者视图的偏移量。也就是说,移动的是视图里面的内容,从上面的例子也可以看出,TextView的文字移动了,而背景色一直没变化,说明不是整个视图在移动。 接着,改一下代码,在xml文件中将TextView的宽高设置成填充父容器。再看一下效果, 这下看的效果就仿佛是在父容器中移动,但是其实还是TextView本身的内容在移动。那这两个方法在实际开发中是如何运用的呢?光凭上面的例子是看不出什么作用的,但是就像文章开头部分说的那样,在视图滑动的情况下,这两个方法发挥了巨大的作用。以类似Launcher左右滑屏为例, 先自定义一个View继承于ViewGroup,如下: [html] view plain copy
  1. /**
  2. *
  3. */
  4. packagecom.kince.scrolldemo;
  5. importandroid.content.Context;
  6. importandroid.util.AttributeSet;
  7. importandroid.view.MotionEvent;
  8. importandroid.view.View;
  9. importandroid.view.ViewGroup;
  10. /**
  11. *@authorkince
  12. *
  13. *
  14. */
  15. publicclassCusScrollViewextendsViewGroup{
  16. privateintlastX=0;
  17. privateintcurrX=0;
  18. privateintoffX=0;
  19. /**
  20. *@paramcontext
  21. */
  22. publicCusScrollView(Contextcontext){
  23. this(context,null);
  24. //TODOAuto-generatedconstructorstub
  25. }
  26. /**
  27. *@paramcontext
  28. *@paramattrs
  29. */
  30. publicCusScrollView(Contextcontext,AttributeSetattrs){
  31. this(context,attrs,0);
  32. //TODOAuto-generatedconstructorstub
  33. }
  34. /**
  35. *@paramcontext
  36. *@paramattrs
  37. *@paramdefStyle
  38. */
  39. publicCusScrollView(Contextcontext,AttributeSetattrs,intdefStyle){
  40. super(context,attrs,defStyle);
  41. //TODOAuto-generatedconstructorstub
  42. }
  43. /*
  44. *(non-Javadoc)
  45. *
  46. *@seeandroid.view.ViewGroup#onLayout(boolean,int,int,int,int)
  47. */
  48. @Override
  49. protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
  50. //TODOAuto-generatedmethodstub
  51. for(inti=0;i<getChildCount();i++){
  52. Viewv=getChildAt(i);
  53. v.layout(0+i*getWidth(),0,getWidth()+i*getWidth(),
  54. getHeight());
  55. }
  56. }
  57. @Override
  58. publicbooleanonTouchEvent(MotionEventevent){
  59. //TODOAuto-generatedmethodstub
  60. switch(event.getAction()){
  61. caseMotionEvent.ACTION_DOWN:
  62. //只考虑水平方向
  63. lastX=(int)event.getX();
  64. returntrue;
  65. caseMotionEvent.ACTION_MOVE:
  66. currX=(int)event.getX();
  67. offX=currX-lastX;
  68. scrollBy(-offX,0);
  69. break;
  70. caseMotionEvent.ACTION_UP:
  71. scrollTo(0,0);
  72. break;
  73. }
  74. invalidate();
  75. returnsuper.onTouchEvent(event);
  76. }
  77. }
这个控件用于水平滑动里面的视图,Activity代码如下: [html] view plain copy
  1. packagecom.kince.scrolldemo;
  2. importandroid.app.Activity;
  3. importandroid.app.ActionBar;
  4. importandroid.app.Fragment;
  5. importandroid.os.Bundle;
  6. importandroid.view.LayoutInflater;
  7. importandroid.view.Menu;
  8. importandroid.view.MenuItem;
  9. importandroid.view.View;
  10. importandroid.view.ViewGroup;
  11. importandroid.view.ViewGroup.LayoutParams;
  12. importandroid.widget.ImageView;
  13. importandroid.widget.ImageView.ScaleType;
  14. importandroid.os.Build;
  15. publicclassLauncherActivityextendsActivity{
  16. privateint[]images={R.drawable.jy1,R.drawable.jy2,R.drawable.jy3,
  17. R.drawable.jy4,R.drawable.jy5,};
  18. privateCusScrollViewmCusScrollView;
  19. @Override
  20. protectedvoidonCreate(BundlesavedInstanceState){
  21. super.onCreate(savedInstanceState);
  22. setContentView(R.layout.activity_launcher);
  23. mCusScrollView=(CusScrollView)this.findViewById(R.id.CusScrollView);
  24. for(inti=0;i<images.length;i++){
  25. ImageViewmImageView=newImageView(this);
  26. mImageView.setScaleType(ScaleType.FIT_XY);
  27. mImageView.setBackgroundResource(images[i]);
  28. mImageView.setLayoutParams(newLayoutParams(
  29. LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
  30. mCusScrollView.addView(mImageView);
  31. }
  32. }
  33. }
在Activity中为CusScrollView添加5个ImageView用于显示图片,xml如下: [html] view plain copy
  1. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical">
  6. <com.kince.scrolldemo.CusScrollView
  7. android:id="@+id/CusScrollView"
  8. android:layout_width="match_parent"
  9. android:layout_height="match_parent">
  10. </com.kince.scrolldemo.CusScrollView>
  11. </LinearLayout>
这个例子对CusScrollView里面的图片进行左右滑动,在onTouchEvent(MotionEvent event)的MotionEvent.ACTION_MOVE中对图片进行移动,使用的是scrollBy()方法,因为手指每次移动都会产生差值,利用scrollBy()方法就可以跟随手指进行左右滑动。在MotionEvent.ACTION_UP事件中,也就是手指抬起时候,直接使用scrollTo()方法让视图回到初始位置。再强调一遍,注意不管是scrollBy()还是scrollTo()方法,都是对CusScrollView内容视图进行移动。效果如下:

(ps:图中女孩是我的老婆,感谢她对我工作的肯定与支持,希望她开开心心、快快乐乐)
至此,就大体完成了对scrollBy()、scrollTo()这两个方法的介绍。不过通过上面的例子,发现一个问题就是滑动速度很快,尤其是scrollTo()方法,几乎是瞬间移动到指定位置。这样倒不能说是缺点,不过在某些情况下,是希望可以缓慢的移动或者有一个明显的移动效果,就像侧滑菜单那样,仿佛有一个移动的动画。这时候Scroller闪亮登场了。 Scroller类是滚动的一个封装类,可以实现View的平滑滚动效果,还可以使用插值器先加速后减速,或者先减速后加速等等效果,而不是瞬间的移动的效果。那是如何实现带动画效果平滑移动的呢?除了Scroller这个类之外,还需要使用View类的computeScroll()方法来配合完成这个过程。看一下这个方法的源码: [html] view plain copy
  1. /**
  2. *CalledbyaparenttorequestthatachildupdateitsvaluesformScrollX
  3. *andmScrollYifnecessary.Thiswilltypicallybedoneifthechildis
  4. *animatingascrollusinga{@linkandroid.widget.ScrollerScroller}
  5. *object.
  6. */
  7. publicvoidcomputeScroll(){
  8. }
从注释中了解到当子视图使用Scroller滑动的时候会调用这个方法,之后View类的mScrollX和mScrollY的值会相应发生变化。并且在绘制View时,会在draw()过程调用该方法。可以看到这个方法是一个空的方法,因此需要子类去重写该方法来实现逻辑,那该方法在何处被触发呢?继续看看View的draw()方法,上面说到会在子视图中调用该方法,也就是说绘制子视图的时候,那么在draw()等等的第四部, [html] view plain copy
  1. //Step4,drawthechildren
  2. dispatchDraw(canvas);
正是绘制子视图,然后看一下这个方法, [html] view plain copy
  1. /**
  2. *Calledbydrawtodrawthechildviews.Thismaybeoverridden
  3. *byderivedclassestogaincontroljustbeforeitschildrenaredrawn
  4. *(butafteritsownviewhasbeendrawn).
  5. *@paramcanvasthecanvasonwhichtodrawtheview
  6. */
  7. protectedvoiddispatchDraw(Canvascanvas){
  8. }
也是一个空方法,但是我们知道这个方法是ViewGroup用来绘制子视图的方法,所以找到View的子类ViewGroup来看看该方法的具体实现逻辑 ,基于篇幅只贴部分代码。 [html] view plain copy
  1. @Override
  2. protectedvoiddispatchDraw(Canvascanvas){
  3. ...
  4. ...
  5. ...
  6. if((flags&FLAG_USE_CHILD_DRAWING_ORDER)==0){
  7. for(inti=0;i<count;i++){
  8. finalViewchild=children[i];
  9. if((child.mViewFlags&VISIBILITY_MASK)==VISIBLE||child.getAnimation()!=null){
  10. more|=drawChild(canvas,child,drawingTime);
  11. }
  12. }
  13. }else{
  14. for(inti=0;i<count;i++){
  15. finalViewchild=children[getChildDrawingOrder(count,i)];
  16. if((child.mViewFlags&VISIBILITY_MASK)==VISIBLE||child.getAnimation()!=null){
  17. more|=drawChild(canvas,child,drawingTime);
  18. }
  19. }
  20. }
  21. //Drawanydisappearingviewsthathaveanimations
  22. if(mDisappearingChildren!=null){
  23. finalArrayList<View>disappearingChildren=mDisappearingChildren;
  24. finalintdisappearingCount=disappearingChildren.size()-1;
  25. //Gobackwards--wemaydeleteasanimationsfinish
  26. for(inti=disappearingCount;i>=0;i--){
  27. finalViewchild=disappearingChildren.get(i);
  28. more|=drawChild(canvas,child,drawingTime);
  29. }
  30. }
  31. ...
  32. ...
  33. ...
  34. }
  35. }
可以看到,在dispatchDraw方法中调用了drawChild(canvas, child, drawingTime)方法,再看一下其代码: [html] view plain copy
  1. protectedbooleandrawChild(Canvascanvas,Viewchild,longdrawingTime){
  2. ...
  3. ...
  4. ...
  5. if(!concatMatrix&&canvas.quickReject(cl,ct,cr,cb,Canvas.EdgeType.BW)&&
  6. (child.mPrivateFlags&DRAW_ANIMATION)==0){
  7. returnmore;
  8. }
  9. child.computeScroll();
  10. finalintsx=child.mScrollX;
  11. finalintsy=child.mScrollY;
  12. booleanscalingRequired=false;
  13. Bitmapcache=null;
  14. ...
  15. ...
  16. ...
  17. }
果然,child.computeScroll(),在这里调用的。也就是ViewGroup在分发绘制自己的孩子的时候,会对其子View调用computeScroll()方法。 回过头来再看一下Scroller,还是先看一下源码(简化),
[html] view plain copy
  1. publicclassScroller{
  2. privateintmMode;
  3. privateintmStartX;
  4. privateintmStartY;
  5. privateintmFinalX;
  6. privateintmFinalY;
  7. privateintmMinX;
  8. privateintmMaxX;
  9. privateintmMinY;
  10. privateintmMaxY;
  11. privateintmCurrX;
  12. privateintmCurrY;
  13. privatelongmStartTime;
  14. privateintmDuration;
  15. privatefloatmDurationReciprocal;
  16. privatefloatmDeltaX;
  17. privatefloatmDeltaY;
  18. privatebooleanmFinished;
  19. privateInterpolatormInterpolator;
  20. privatefloatmVelocity;
  21. privatefloatmCurrVelocity;
  22. privateintmDistance;
  23. privatefloatmFlingFriction=ViewConfiguration.getScrollFriction();
  24. privatestaticfinalintDEFAULT_DURATION=250;
  25. privatestaticfinalintSCROLL_MODE=0;
  26. privatestaticfinalintFLING_MODE=1;
  27. /**
  28. *CreateaScrollerwiththedefaultdurationandinterpolator.
  29. */
  30. publicScroller(Contextcontext){
  31. this(context,null);
  32. }
  33. /**
  34. *CreateaScrollerwiththespecifiedinterpolator.Iftheinterpolatoris
  35. *null,thedefault(viscous)interpolatorwillbeused."Flywheel"behaviorwill
  36. *beineffectforappstargetingHoneycombornewer.
  37. */
  38. publicScroller(Contextcontext,Interpolatorinterpolator){
  39. this(context,interpolator,
  40. context.getApplicationInfo().targetSdkVersion>=Build.VERSION_CODES.HONEYCOMB);
  41. }
  42. /**
  43. *CreateaScrollerwiththespecifiedinterpolator.Iftheinterpolatoris
  44. *null,thedefault(viscous)interpolatorwillbeused.Specifywhetheror
  45. *nottosupportprogressive"flywheel"behaviorinflinging.
  46. */
  47. publicScroller(Contextcontext,Interpolatorinterpolator,booleanflywheel){
  48. mFinished=true;
  49. mInterpolator=interpolator;
  50. mPpi=context.getResources().getDisplayMetrics().density*160.0f;
  51. mDeceleration=computeDeceleration(ViewConfiguration.getScrollFriction());
  52. mFlywheel=flywheel;
  53. mPhysicalCoeff=computeDeceleration(0.84f);//lookandfeeltuning
  54. }
  55. /**
  56. *Callthiswhenyouwanttoknowthenewlocation.Ifitreturnstrue,
  57. *theanimationisnotyetfinished.
  58. */
  59. publicbooleancomputeScrollOffset(){
  60. if(mFinished){
  61. returnfalse;
  62. }
  63. inttimePassed=(int)(AnimationUtils.currentAnimationTimeMillis()-mStartTime);
  64. if(timePassed<mDuration){
  65. switch(mMode){
  66. caseSCROLL_MODE:
  67. floatx=timePassed*mDurationReciprocal;
  68. if(mInterpolator==null)
  69. x=viscousFluid(x);
  70. else
  71. x=mInterpolator.getInterpolation(x);
  72. mCurrX=mStartX+Math.round(x*mDeltaX);
  73. mCurrY=mStartY+Math.round(x*mDeltaY);
  74. break;
  75. caseFLING_MODE:
  76. finalfloatt=(float)timePassed/mDuration;
  77. finalintindex=(int)(NB_SAMPLES*t);
  78. floatdistanceCoef=1.f;
  79. floatvelocityCoef=0.f;
  80. if(index<NB_SAMPLES){
  81. finalfloatt_inf=(float)index/NB_SAMPLES;
  82. finalfloatt_sup=(float)(index+1)/NB_SAMPLES;
  83. finalfloatd_inf=SPLINE_POSITION[index];
  84. finalfloatd_sup=SPLINE_POSITION[index+1];
  85. velocityCoef=(d_sup-d_inf)/(t_sup-t_inf);
  86. distanceCoef=d_inf+(t-t_inf)*velocityCoef;
  87. }
  88. mCurrVelocity=velocityCoef*mDistance/mDuration*1000.0f;
  89. mCurrX=mStartX+Math.round(distanceCoef*(mFinalX-mStartX));
  90. //PintomMinX<=mCurrX<=mMaxX
  91. mCurrX=Math.min(mCurrX,mMaxX);
  92. mCurrX=Math.max(mCurrX,mMinX);
  93. mCurrY=mStartY+Math.round(distanceCoef*(mFinalY-mStartY));
  94. //PintomMinY<=mCurrY<=mMaxY
  95. mCurrY=Math.min(mCurrY,mMaxY);
  96. mCurrY=Math.max(mCurrY,mMinY);
  97. if(mCurrX==mFinalX&&mCurrY==mFinalY){
  98. mFinished=true;
  99. }
  100. break;
  101. }
  102. }
  103. else{
  104. mCurrX=mFinalX;
  105. mCurrY=mFinalY;
  106. mFinished=true;
  107. }
  108. returntrue;
  109. }
  110. /**
  111. *Startscrollingbyprovidingastartingpointandthedistancetotravel.
  112. *Thescrollwillusethedefaultvalueof250millisecondsforthe
  113. *duration.
  114. *
  115. *@paramstartXStartinghorizontalscrolloffsetinpixels.Positive
  116. *numberswillscrollthecontenttotheleft.
  117. *@paramstartYStartingverticalscrolloffsetinpixels.Positivenumbers
  118. *willscrollthecontentup.
  119. *@paramdxHorizontaldistancetotravel.Positivenumberswillscrollthe
  120. *contenttotheleft.
  121. *@paramdyVerticaldistancetotravel.Positivenumberswillscrollthe
  122. *contentup.
  123. */
  124. publicvoidstartScroll(intstartX,intstartY,intdx,intdy){
  125. startScroll(startX,startY,dx,dy,DEFAULT_DURATION);
  126. }
  127. /**
  128. *Startscrollingbyprovidingastartingpoint,thedistancetotravel,
  129. *andthedurationofthescroll.
  130. *
  131. *@paramstartXStartinghorizontalscrolloffsetinpixels.Positive
  132. *numberswillscrollthecontenttotheleft.
  133. *@paramstartYStartingverticalscrolloffsetinpixels.Positivenumbers
  134. *willscrollthecontentup.
  135. *@paramdxHorizontaldistancetotravel.Positivenumberswillscrollthe
  136. *contenttotheleft.
  137. *@paramdyVerticaldistancetotravel.Positivenumberswillscrollthe
  138. *contentup.
  139. *@paramdurationDurationofthescrollinmilliseconds.
  140. */
  141. publicvoidstartScroll(intstartX,intstartY,intdx,intdy,intduration){
  142. mMode=SCROLL_MODE;
  143. mFinished=false;
  144. mDuration=duration;
  145. mStartTime=AnimationUtils.currentAnimationTimeMillis();
  146. mStartX=startX;
  147. mStartY=startY;
  148. mFinalX=startX+dx;
  149. mFinalY=startY+dy;
  150. mDeltaX=dx;
  151. mDeltaY=dy;
  152. mDurationReciprocal=1.0f/(float)mDuration;
  153. }
  154. /**
  155. *Startscrollingbasedonaflinggesture.Thedistancetravelledwill
  156. *dependontheinitialvelocityofthefling.
  157. *
  158. *@paramstartXStartingpointofthescroll(X)
  159. *@paramstartYStartingpointofthescroll(Y)
  160. *@paramvelocityXInitialvelocityofthefling(X)measuredinpixelsper
  161. *second.
  162. *@paramvelocityYInitialvelocityofthefling(Y)measuredinpixelsper
  163. *second
  164. *@paramminXMinimumXvalue.Thescrollerwillnotscrollpastthis
  165. *point.
  166. *@parammaxXMaximumXvalue.Thescrollerwillnotscrollpastthis
  167. *point.
  168. *@paramminYMinimumYvalue.Thescrollerwillnotscrollpastthis
  169. *point.
  170. *@parammaxYMaximumYvalue.Thescrollerwillnotscrollpastthis
  171. *point.
  172. */
  173. publicvoidfling(intstartX,intstartY,intvelocityX,intvelocityY,
  174. intminX,intmaxX,intminY,intmaxY){
  175. //Continueascrollorflinginprogress
  176. if(mFlywheel&&!mFinished){
  177. floatoldVel=getCurrVelocity();
  178. floatdx=(float)(mFinalX-mStartX);
  179. floatdy=(float)(mFinalY-mStartY);
  180. floathyp=FloatMath.sqrt(dx*dx+dy*dy);
  181. floatndx=dx/hyp;
  182. floatndy=dy/hyp;
  183. floatoldVelocityX=ndx*oldVel;
  184. floatoldVelocityY=ndy*oldVel;
  185. if(Math.signum(velocityX)==Math.signum(oldVelocityX)&&
  186. Math.signum(velocityY)==Math.signum(oldVelocityY)){
  187. velocityX+=oldVelocityX;
  188. velocityY+=oldVelocityY;
  189. }
  190. }
  191. mMode=FLING_MODE;
  192. mFinished=false;
  193. floatvelocity=FloatMath.sqrt(velocityX*velocityX+velocityY*velocityY);
  194. mVelocity=velocity;
  195. mDuration=getSplineFlingDuration(velocity);
  196. mStartTime=AnimationUtils.currentAnimationTimeMillis();
  197. mStartX=startX;
  198. mStartY=startY;
  199. floatcoeffX=velocity==0?1.0f:velocityX/velocity;
  200. floatcoeffY=velocity==0?1.0f:velocityY/velocity;
  201. doubletotalDistance=getSplineFlingDistance(velocity);
  202. mDistance=(int)(totalDistance*Math.signum(velocity));
  203. mMinX=minX;
  204. mMaxX=maxX;
  205. mMinY=minY;
  206. mMaxY=maxY;
  207. mFinalX=startX+(int)Math.round(totalDistance*coeffX);
  208. //PintomMinX<=mFinalX<=mMaxX
  209. mFinalX=Math.min(mFinalX,mMaxX);
  210. mFinalX=Math.max(mFinalX,mMinX);
  211. mFinalY=startY+(int)Math.round(totalDistance*coeffY);
  212. //PintomMinY<=mFinalY<=mMaxY
  213. mFinalY=Math.min(mFinalY,mMaxY);
  214. mFinalY=Math.max(mFinalY,mMinY);
  215. }
  216. }
Scroller有三个构造方法,其中二、三可以使用动画插值器。除了构造方法外,Scroller还有以下几个重要方法:computeScrollOffset()、startScroll(int startX, int startY, int dx, int dy, int duration)、fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)等。 startScroll(int startX, int startY, int dx, int dy, int duration)从方法名字来看应该是滑动开始的地方,事实上我们在使用的时候也是先调用这个方法的,它的作用是:(startX , startY)在duration时间内前进(dx,dy)个单位,即到达坐标为(startX+dx , startY+dy) 但是从源码来看, [html] view plain copy
  1. publicvoidstartScroll(intstartX,intstartY,intdx,intdy,intduration){
  2. mMode=SCROLL_MODE;
  3. mFinished=false;
  4. mDuration=duration;
  5. mStartTime=AnimationUtils.currentAnimationTimeMillis();
  6. mStartX=startX;
  7. mStartY=startY;
  8. mFinalX=startX+dx;
  9. mFinalY=startY+dy;
  10. mDeltaX=dx;
  11. mDeltaY=dy;
  12. mDurationReciprocal=1.0f/(float)mDuration;
  13. }
这个方法更像是一个构造方法用来初始化赋值的,比如设置滚动模式、开始时间,持续时间、起始坐标、结束坐标等等,并没有任何对View的滚动操作,当然还有一个重要的变量:mDurationReciprocal。因为这个变量要在接下来介绍的computeScrollOffset()方法使用,computeScrollOffset()方法主要是根据当前已经消逝的时间来计算当前的坐标点,并且保存在mCurrX和mCurrY值中,那这个消逝的时间就是如何计算出来的呢?之前在startScroll()方法的时候获取了当前的动画毫秒并赋值给了mStartTime,在computeScrollOffset()中再一次调用AnimationUtils.currentAnimationTimeMillis()来获取动画毫秒减去mStartTime就是消逝时间了。然后进去if判断,如果动画持续时间小于设置的滚动持续时间mDuration,则是SCROLL_MODE,再根据Interpolator来计算出在该时间段里面移动的距离,移动的距离是根据这个消逝时间乘以mDurationReciprocal,就得到一个相对偏移量,再进行Math.round(x * mDeltaX)计算,就得到最后的偏移量,然后赋值给mCurrX, mCurrY,所以mCurrX、 mCurrY的值也是一直变化的。总结一下该方法的作用就是,计算在0到mDuration时间段内滚动的偏移量,并且判断滚动是否结束,true代表还没结束,false则表示滚动结束了。 之前说到是Scroller配合computeScroll()方法来实现移动的,那是如何配合的呢? 1、首先调用Scroller的startScroll()方法来进行一些滚动的初始化设置, [html] view plain copy
  1. scroller.startScroll(getScrollX(),0,distance,0);
2、然后调用View的invalidate()或postInvalidate()进行重绘。 [html] view plain copy
  1. invalidate();//刷新视图

3、绘制View的时候会触发computeScroll()方法,接着重写computeScroll(),在computeScroll()里面先调用Scroller的computeScrollOffset()方法来判断滚动是否结束,如果滚动没有结束就调用scrollTo()方法来进行滚动。 [html] view plain copy
  1. @Override
  2. blicvoidcomputeScroll(){
  3. if(scroller.computeScrollOffset()){
  4. scrollTo(scroller.getCurrX(),0);
  5. }

4、scrollTo()方法虽然会重新绘制View,但是还是要手动调用下invalidate()或者postInvalidate()来触发界面重绘,重新绘制View又触发computeScroll(),所以就进入一个递归循环阶段,这样就实现在某个时间段里面滚动某段距离的一个平滑的滚动效果。 [html] view plain copy
  1. @Override
  2. publicvoidcomputeScroll(){
  3. if(scroller.computeScrollOffset()){
  4. scrollTo(scroller.getCurrX(),0);
  5. invalidate();
  6. }
  7. }
具体流程图如下:

了解完Scroller之后,我们就对之前的例子进行一下改进,不直接使用scrollTo()、ScrollBy()方法了,而是使用Scroller来实现一个平滑的移动效果。只需把代码稍微改一下就可以了,如下: [html] view plain copy
  1. /**
  2. *
  3. */
  4. packagecom.kince.scrolldemo;
  5. importandroid.content.Context;
  6. importandroid.util.AttributeSet;
  7. importandroid.view.MotionEvent;
  8. importandroid.view.View;
  9. importandroid.view.ViewGroup;
  10. importandroid.widget.Scroller;
  11. /**
  12. *@authorkince
  13. *
  14. *
  15. */
  16. publicclassCusScrollViewextendsViewGroup{
  17. privateintlastX=0;
  18. privateintcurrX=0;
  19. privateintoffX=0;
  20. privateScrollermScroller;
  21. /**
  22. *@paramcontext
  23. */
  24. publicCusScrollView(Contextcontext){
  25. this(context,null);
  26. //TODOAuto-generatedconstructorstub
  27. }
  28. /**
  29. *@paramcontext
  30. *@paramattrs
  31. */
  32. publicCusScrollView(Contextcontext,AttributeSetattrs){
  33. this(context,attrs,0);
  34. //TODOAuto-generatedconstructorstub
  35. }
  36. /**
  37. *@paramcontext
  38. *@paramattrs
  39. *@paramdefStyle
  40. */
  41. publicCusScrollView(Contextcontext,AttributeSetattrs,intdefStyle){
  42. super(context,attrs,defStyle);
  43. //TODOAuto-generatedconstructorstub
  44. mScroller=newScroller(context);
  45. }
  46. /*
  47. *(non-Javadoc)
  48. *
  49. *@seeandroid.view.ViewGroup#onLayout(boolean,int,int,int,int)
  50. */
  51. @Override
  52. protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
  53. //TODOAuto-generatedmethodstub
  54. for(inti=0;i<getChildCount();i++){
  55. Viewv=getChildAt(i);
  56. v.layout(0+i*getWidth(),0,getWidth()+i*getWidth(),
  57. getHeight());
  58. }
  59. }
  60. @Override
  61. publicbooleanonTouchEvent(MotionEventevent){
  62. //TODOAuto-generatedmethodstub
  63. switch(event.getAction()){
  64. caseMotionEvent.ACTION_DOWN:
  65. //只考虑水平方向
  66. lastX=(int)event.getX();
  67. returntrue;
  68. caseMotionEvent.ACTION_MOVE:
  69. currX=(int)event.getX();
  70. offX=currX-lastX;
  71. //scrollBy(-offX,0);
  72. mScroller.startScroll(getScrollX(),0,-offX,0);
  73. break;
  74. caseMotionEvent.ACTION_UP:
  75. //scrollTo(0,0);
  76. mScroller.startScroll(getScrollX(),0,-100,0);
  77. break;
  78. }
  79. invalidate();
  80. returnsuper.onTouchEvent(event);
  81. }
  82. @Override
  83. publicvoidcomputeScroll(){
  84. //TODOAuto-generatedmethodstub
  85. if(mScroller.computeScrollOffset()){
  86. scrollTo(mScroller.getCurrX(),0);
  87. invalidate();
  88. }
  89. }
  90. }


这样就实现了一个平滑的移动效果。关于scrollTo() 、scrollBy()、 Scroller讲解就进行到这里。之后会更新两篇关于这方面的UI效果开发,一篇是模仿Zaker的开门效果;另一篇是首页推荐图片轮播效果。

















更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. android上一些方法的区别和用法的注意事项
  5. android实现字体闪烁动画的方法
  6. Android(安卓)matrix 控制图片的旋转、缩放、移动
  7. Android中dispatchDraw分析
  8. Android四大基本组件介绍与生命周期
  9. Android(安卓)MediaPlayer 常用方法介绍

随机推荐

  1. Android(安卓)Bundle类
  2. AndroidManifest.xml【 manifest -> perm
  3. Android(安卓)Studio 3.0以后打包修改文
  4. [Android] 环境优化配置Android(安卓)Stu
  5. Android(安卓)studio maven pom.xml
  6. Android(安卓)启动页,倒计时 view
  7. Android传感器介绍
  8. android图片缩放手势检测类--ScaleGestur
  9. android stdio Error Could not find com
  10. android 开发工具下载地址