在本篇文章的前面,我们讲到了使用Layout的方法实现View的滑动今天给大家介绍一下使用ScrollTo,ScrollBy和Scroller来实现View的滑动。
一、ScrollTo,ScrollBy
在View中,系统专门提供了scrollTo和scrollBy两种方式来改变View的位置,于是我们就可以通过这两个方法实现View的滑动。
我们先来看一下这两个方法的实现:
/** * Set the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the x position to scroll to * @param y the y position to scroll to */ public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { postInvalidateOnAnimation(); } } } /** * Move the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the amount of pixels to scroll by horizontally * @param y the amount of pixels to scroll by vertically */ public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
好了,知道了上面这些,那我们就来写一写。
前面的onTouchEvent()代码相同,就不再赘述。
核心代码如下:
int offsetX=x-lastX; int offsetY=y-lastY; view.scrollBy(offsetX,offsetY);
但是!问题出现了,当我们拖动View的时候发现View并没有移动,难道是我们代码写错了?
其实,我们的代码并没有写错,而且View也确实移动了,只是它移动的并不是我们想要移动的东西。scrollTo,scrollBy方法移动的是View的Content,即让View的内容移动,如果在ViewGroup中使用scrollTo,scrollBy方法,那么移动的将是所有的子View,但如果在View中使用的话,那么移动的将是View的内容。例如TextView,它的content是它的文本;Image View的content是他的Drawable对象。
通过上面的解释,相信大家应该知道为什么我们的View看着没有移动了,现在我们更改一下上面的代码。
((View)getParent()).scrollBy(offsetX,offsetY);
让我们再试一下,咦?发现view是移动了,怎么感觉它是在乱动呢,并不像我们想象的那样跟随触摸点的移动而移动。
这里大家需要理解一下视图移动的一些知识。大家在理解这个问题的时候,不妨这样想象手机屏幕是一个中空的盖板,下面是一个巨大的画布,也就是我们想要显示的视图。当把这个盖板盖在画布的某一处时,透过中空的矩形,我们看见了手机屏幕上显示的视图,而画布上其他位置的视图,则被盖板盖住而看不见。我们的视图与这个例子非常类似,我们没有看见视图,并不代表它不存在,有可能只是在屏幕外边而已。当调用scrollBy方法时,可以认为是外面的盖板在移动。
通过上面的理解,我们验证一下,我们使用scrollBy(20,10),偏移量均为X轴,Y轴上的正数,但是在屏幕的可视区域,可以看到View却向X轴、Y轴的负方向移动。正好证明了上面的解释。这是因为坐标系选取的不同,而产生的效果。
所以,通过上面的验证和理解,我们可以得出结论,如果想要实现跟随手指而滑动的效果,那就必须使用负值的参数,代码如下:
int offsetX=x-lastX; int offsetY=y-lastY; ((View)getParent()).scrollBy(-offsetX,-offsetY);
再去验证一下,大家就可以发现效果与View的滑动之一中的效果一样了,类似的,使用绝对坐标时,也可以使用scrollTo()方法来实现这一效果。
二、Scroller
弹性滑动对象,用于实现View的弹性滑动,我们都知道,当时用View的scrollTo和scrollBy方法来进行滑动时,其过程时瞬间完成的,这个效果让人感觉非常突兀,因此,scroller应运而生,通过scroller类可以实现平滑移动的效果,而不再是瞬间完成的了。
说起scroller的实现原理,我们不妨细细考虑一下,其实它与scrollTo和scrollBy方法实现子View的移动 的原理是基本相似的。虽说scrollBy方法是让子View从某点瞬间移动到另一点,但是如果每次都是微笑的偏移量的话,就会形成一个平滑移动的效果。这个原理与动画的实现原理是基本类似的,它利用的是人眼的视觉暂留特性。
scroller本身无法让View弹性滑动,他需要和View的computeScroll方法配合使用。
步骤如下:
1.初始化Scroller
首先,通过它的构造方法创建一个Scroller对象:
Scroller mScroller=new Scroller(context);2.重写computeScroll方法,实现弹性滑动。
computeScroll是Scroller类的核心,系统在绘制的时候,会在draw()方法中调用该方法。这个方法实际上就是使用的scrollTo方法,再结合scroller对象,帮助获取到当前的滚动值,然后通过不断的瞬间移动一个小的举例来实现整体上的平滑移动效果。
@Override public void computeScroll(){ super.computeScroll(); //判断Scroller是否执行完毕 if(mScroller.computeScrollOffset){ ((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); postInvalidate();//通过重绘来不断调用computeScroll() } }
Scroller 类提供了computeScrollOffset方法来判断是否完成整个滑动,同时也提供了getCurrX()和getCurrY()方法来获得当前滑动的坐标。在上面的代码中,唯一需要注意的是postInvalidate()方法,因为只能够在computeScroll方法中获取滑动过程中的scroll X和scrollY坐标,但是computeScroll方法是不会自动给调用的,只能通过invalidate->draw->computeScroll方法来间接调用computeScroll方法,所以需要使用postInvalidate或是Invalidate来循环间接调用computeScroll,直至mScroller.computeScrollOffset返回false,完成整个平滑移动过程。
最后,我们只需要调用startScroll开启弹性滑动过程。
startScroll()方法有两个重载方法:
public void startScroll(int startX,int startY, int dy, int duration)
public void startScroll(int startX, int startY, int dx, int dy)
两个的区别是可以指定滑动持续的事件。
现在我们来实现一个平滑的方法,代码如下:
private void smoothScrollTo(int destX,int destY){ int scrollX=viewGroup.getScrollX(); int deltaX=destX-scrollX; int scrollY=viewGroup.getScrollY(); int deltaY=destY-scrollY; //1000ms内滑向dest mScroller.startScroll(scrollX,scrollY,-deltaX,-deltaY,1000);//ViewGroup的偏移量参数和ScrollBy一样需要是负数 invalidate();//需要先进行重绘,从而开始调用computeScroll,开始循环弹性滑动过程 }好了,今天的View的滑动就介绍到这里,后面会给大家介绍使用动画和属性动画来实现平滑移动效果。