view的滑动与事件分发

view的滑动方式

通过Scroller来进行滑动

view 自身就提供了两个方法通过Scroller来实现滑动:scrollTo和scrollBy ,而scrollBy本身也是通过调用scrollTo 来实现,scrollTo传入的参数是想要滑动到的目的坐标 x,y ,这里的 x,y并不是view 中内容相对于 view 位置原点的坐标,这也是很多人会认为为什么这里的移动规则怎么会是相反的,可以结合着源码看下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();
}
}
}
public void scrollBy(int x, int y){
scrollTo(mScrollX + x,mScrollY + y);
}

这里 mScrollX,mScrollY存放的是 View本身相对于 View 的内容的距离,这个距离可正可负,当 View 的内容处于 View 本身的左边时mScrollX就为正,一般我就记以 view 内容的左上角的点为原点,view位置左上角的点相对于 view 的内容原点的位置,这样子记就不容易弄错。但原理还是要知道。如图所示

使用动画来进行滑动

这里可以使用 View 动画和属性动画进行滑动,需要注意的是:一般在动画中也会讲,View 动画只是改变了 View 的影像,也就是说当 View 移动到其他位置时就不能响应用户的点击事件(如果这是一个可响应点击事件的 View),因为 View 的本体还在原来的位置,要解决这个问题就可以使用属性动画来解决,属性动画是在 Api 11的引进的,现在也基本上绝大多数安卓手机机型在3.0以上,如果还要适配低版本的机型就可以使用Jake Wharton的开源库: nineoldandroid来实现。这里具体动画的实现就不再讲了

改变View的布局参数

如果要真的实现 View 本身的移动则可以改变 View 的布局,View 的绘制过程经过三个步骤:Measure,Layout,Draw,那就可以改变 view 的布局参数再调用requestLayout函数来使得 View 重新 layout。

View 的事件分发机制

点击事件的传递顺序

任何点击事件都离不开三个重要方法:dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent().
dispatchTouchEvent()用来进行事件的分发,该方法一定会被调用。
onInterceptTouchEvent()表示是否拦截当前触摸事件。
onTouchEvent用来消耗当前触摸事件,如果返回 false 则表示不消耗当前事件。
这三者的关系用伪代码可以 很好地表示他们之间的关系:

1
2
3
4
5
6
7
8
9
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvnet){
consume = onTouchEvnet();
}else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}

当一个点击事件在屏幕上触发时,它的传递顺序是Activity->Window->View,这很好理解,从父容器不断的往里面传递,传递到最底层的子 View 则调用 onTouchEvent 方法,不被消耗则向上传递。

有多个 ViewGroup 嵌套着的 View 的响应点击事件都是这个流程:先进行分发,再询问当前 ViewGroup 是否拦截该点击事件,如果拦截则交由onTouchEvent()方法处理,如果不拦截则分发到子 ViewGroup,onTouchEvent()返回 ture 则表示已消耗当前事件,false 则表示不消耗当前事件,传递到父 ViewGroup 的onTouchEvent方法里面,如果 父ViewGroup 为顶层 View 则这一个事件序列将会丢失。

参考:

Android开发艺术探索