View的滑动有七种方法,我想重点记录一下其中的两种,其实也可以说是一种,Scroll相关。
先列举其他几种吧:
1、layout()方法,直接调用layout方法使得子view重新布局。
2、offsetLeftAndRight()和offsetTopAndBottom(),类似layout()方法。
3、layoutParams。
4、Animator属性动画
5、ViewDragHelper,这个下一篇详细记录。
下面开始进入Scroller了:
一、ScrollTo()和ScrollBy()
在使用scroller之前先了解一下这两个方法。
它们是用来对view里面的内容进行滚动的,滚动的方向和给的值正负相反。
有的资料说这是因为,移动的其实是手机屏幕。
所以用户看到的内容就会相对反向运动了。我感觉不是很正确,不过这么理解倒是对使用没有什么影响,这里有一篇资料我觉得蛮有道理的,不过我只是半懂。http://www.tuicool.com/articles/z6rmim
好了,看一下我的自定义view的源码吧:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
curX = (int) event.getRawX();
curY = (int) event.getRawY();
int offsetX = curX - lastX;
int offsetY = curY - lastY;
((View) getParent()).scrollBy(0 - offsetX, 0 - offsetY);
lastX = curX;
lastY = curY;
break;
case MotionEvent.ACTION_UP:
View viewGroup=(View)getParent();
scroller.startScroll(viewGroup.getScrollX(), viewGroup.getScrollY(), -viewGroup.getScrollX(), -viewGroup.getScrollY());
break;
default:
break;
}
return true;
}
最后的action_up那里不用管,重点是action_move。
由于之前说过了,移动的是view内部的内容所以在使用scrollBy的时候就先得到当前view的父view。然后通过计算手指移动距离进行scroll就好了。
二、Scroller
好了,在实现完action_move后,其实视图就可以随着手指滑动了。
但是如果是通过按钮,一个button来实现滑动,那么上面说的两个方法就会很突兀。
这就是为什么要使用scroller的原因。
跟scroller相关的方法有三个:
startScroll()
computeScroll()
computeScrollOffset()
哦,还有个构造方法,,
假设构造好了,Scroller scroller=new Scroller(context);
接下来就是在适当的时候调用startScroll()方法了。
这里我们在action_up那里调用,实现的效果就是让跟随手指滑动的view在手指松开后自动回去。
代码如下:
case MotionEvent.ACTION_UP:
View viewGroup=(View)getParent();
scroller.startScroll(viewGroup.getScrollX(), viewGroup.getScrollY(), -viewGroup.getScrollX(), -viewGroup.getScrollY());
invalidate();
break;
这里又涉及到了另一个方法,getScrollX(),这个可以理解成是用来得到试图左上角和屏幕左上角的距离的方法。
比如本来视图和屏幕是重合的,结果调用scrollTo(-10,-10)使得试图左移了10px,那么这个时候就不重合了,于是getScrollX()就会返回10。显然,这个方法其实记录了滑动的时候起点和终点的直线距离,有点类似物理里面的位移。
解决了这个方法后,再来看startScroll的四个参数
public void startScroll(int startX, int startY, int dx, int dy)
从名字很好猜测,分别是起始x,y和需要滑动的距离x,y。
接下来看这个函数的代码发现,只是对一些变量进行了操作,并没有开启什么动画或者滑动。
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
可以看到,就是设置了一下startX,finalX和滑动时间这种的,所以并没有真的像名字描述的那样开始滑动。
那么是什么时候开始的呢,是在使用了这个函数的下面一排,invalidate()。
invalidate()会调用draw(),draw会调用computeScroll()。
这个computeScroll()是需要我们重写的,代码如下:
@Override
public void computeScroll() {
// TODO Auto-generated method stub
super.computeScroll();
if (scroller.computeScrollOffset()) {
((View) getParent()).scrollTo(scroller.getCurrX(),
scroller.getCurrY());
}
invalidate();
}
可以发现,到这里,提到的三个函数全部都出现了,这里先说computeScrollOffset()方法吧。
它会返回一个boolean,如果滑动结束了,就返回false,反之返回true。它是通过什么判断的呢,是通过之前在startScroll里面设置的时间来进行判断的。
我截取源代码中的几句展示出来,多了看的头晕。
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = timePassed * mDurationReciprocal;
if (mInterpolator == null)
x = viscousFluid(x);
else
x = mInterpolator.getInterpolation(x);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
只需要注意到,判断条件是时间,操作中更改了mCurrx这些属性就好了。
理解了这个方法之后,对于刚才的computeScroll()就清晰 很多,下面是computeScroll在判断完毕之后我们自己实现的代码:
if (scroller.computeScrollOffset()) {
((View) getParent()).scrollTo(scroller.getCurrX(),
scroller.getCurrY());
}
invalidate();
很简单,滑到计算的那个地方去。
下面,总结一下绘制的流程。
view捕捉到action_up,调用startScroll()来初始化滑动参数。
然后通过invalidate()调用draw()。
draw会调用computeScroll()。
computeScroll()首先通过cmoputeScrollOffset()判断是否结束,同时改变滑动位置的值,接着调用scrollTo进行滑动。
最后再次通过invalidate()来调用draw,完成循环的过程。