View 的事件体系

579 阅读11分钟
原文链接: blog.csdn.net

最近一直在看VIew的相关知识,因为马上要找工作了,所以还是总结下来,对以后也能有所帮助。本文理论知识选自Android开发艺术探索

View参数概念

1.     位置参数

由顶点决定,对应于View的四个属性: top(左上角纵坐标)、left(左上角横坐标)、bottom(右下角纵坐标)、right(右下角横坐标),相对于View的父容器,view的宽高 width = right – left,height= bottom – top

获取:getLeft(),getRight(), getTop(), getBottom();

从Android3.0开始新增x, y, translationX, translationY,x,y表示view相对于父容器的左上角坐标,translationX, translationY表示view相对于父容器的偏移量,默认值为0

top和left表示原始的位置信息,在平移过程中,translationX、translationY发生改变,进而x, y改变。

2.     MotionEvent(位移事件)

ACTION_DOWN:手指刚接触屏幕

ACTION_MOVE:手指在屏幕上移动

ACTION_UP:手指离开屏幕

getX(),getY() 点击事件相对于当前View的左上角的x和y坐标 getRawX(), getRawY() 点击事件相对于屏幕左上角的x和y坐标

3.     TouchSlop 系统能识别的最小滑动距离,通过ViewConfiguration.get(getContext()).getScaledTouchSlop获取

4.     VelocityTracker追踪手指滑动过程中的速度

在onTouchEvent中追踪点击事件的速度

VelocityTrackervelocityTracker = VelocityTracker.obtain();

velocityTracker.addMovement(event);

velocityTracker.computeCurrentVelocity(1000),1000ms内划过的像素

velocityTracker.getXVelocity();水平速度

velocityTracker.getXVelocity();竖直速度

velocityTracker.clear();

velocityTracker.recycle();

5.     GestureDetector 手势检测 检测单击、双击、长按、滑动

onSingleTapUp(单击)、onFailing(快速滑动)、onScroll(拖动)、onLongPress(长按)、onDoubleTap(双击)

6.     Scroller 弹性滑动对象 有过渡效果的滑动,以下是固定写法,具体后面会讨论

  1. Scroller scroller = new Scroller(context);  
  2. private void smoothScrollTo(int x, int y){  
  3.     int detal = x – getScrollX();  
  4.     scroller.startScroll(getScrollX(), 0, detal, 01000);  
  5.     invalidate();  
  6. }  
  7. public void computeScroll(){  
  8.     if(mScroller.computeScrollOffset()){  
  9.         scrollTo(mScroller.getCurrentX(), mScroller.getCurrentY());  
  10.         postInvalidate();  
  11.     }  
  12. }  
Scroller scroller = new Scroller(context);
private void smoothScrollTo(int x, int y){
	int detal = x – getScrollX();
	scroller.startScroll(getScrollX(), 0, detal, 0, 1000);
	invalidate();
}
public void computeScroll(){
	if(mScroller.computeScrollOffset()){
		scrollTo(mScroller.getCurrentX(), mScroller.getCurrentY());
		postInvalidate();
	}
}
二、View的滑动

1.     scrollTo& scrollBy

关键属性mScrollX,mScrollY 分别通过getScrollX()和getScrollY()得到

mScrollX指View左边缘与View内容左边缘在水平方向的距离

mScrollY指View上边缘与View内容上边缘在竖直方向的距离

当View左移时,mScrollX为正,反之为负

当View上移时,mScrollY为正,反之为负

scrollTo和scrollBy只能改变View内容的位置,不能改变View在布局中的位置。

2.     使用动画 View动画和属性动画

View动画  向右下角平移100

 

 View动画是对View的影像做操作,并没有真正改变View的位置参

数,动画完成后结果会消失,设置fillAfter属性为true会保留动画后的状态,移动后的View无法触发onClick事件

属性动画 View从原位置向右平移100像素


属性动画后动画结果不会消失,并且在平移动画之后,能够触发onClick事件

3.     改变布局参数

改变LayoutParams,通过设置margin参数即可达到移动的效果。

 

scrollTo/scrollBy: 适合对View内容的滑动

动画: 适合没有交互的View和复杂的动画效果

改变布局参数: 适合有交互的View

三、弹性滑动

1.     探究Scoller

当构造一个Scroller对象并调用它的startScroll方法时,Scroller内部其实什么也没做,只是传递了几个参数并保存了,而invalidate方法会导致View重绘,draw方法又会调用computeScroll方法,而在computeScroll中通过scrollTo方法让View滑动,接着又调用postInvalidate方法再次重绘,直到滑动过程结束。

       而在computeScroll方法中,通过computeScrollOffset判断当前是否重绘结束,通过时间的流逝来计算当前View的位置坐标。如果经过的时间小于总时间,继续重绘,直到时间达到动画的总时间,重绘结束,同时滑动完成。

       View的重绘距滑动起始会有一个时间间隔,通过这个时间间隔就能得出当前View的滑动位置,然后通过scrollTo方法完成View的滑动,这样每次重绘都会让View小幅度滑动,多次就成了弹性滑动。

2.     使用动画

通过动画的每一帧的到来获取动画完成的比例,然后根据这个比例计算View应该滑动的距离。

 

3.     延时策略

通过Handler或postDelayed,或者在线程中通过while和sleep来不断的发送消息,在消息中进行View的滑动。

四、View的事件分发机制

1.     传递规则

分发过程即将MotionEvent传递到一个具体的View,由三个重要的方法共同完成,dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent。

       public booleandispatchTouchEvent(MotionEvent ev)

       事件传递给当前View一定会执行这个方法,返回结果与View的onTouchEvent和下级View的dispatchTouchEvent有关,表示是否消耗当前的事件。

       public booleanonInterceptTouchEvent(MotionEvent ev)

       判断是否拦截某个事件,在dispatchTouchEvent方法中调用。如果View拦截了某个事件,那么在同一事件序列当中,该方法不会再被调用

       public boolean onTouchEvent(MotionEventev)

       处理点击事件,判断是否消耗了事件,如果不消耗,那在同一事件序列中,当前View不会再接收到事件。

这三个方法的关系可以用以下伪代码表示

 

事件的传递规则:对于根ViewGroup来说,点击事件首先会传给它,此时调用它的dispatchTouchEvent方法,然后执行onInterceptTouchEvent方法,如果该方法返回true,表示它要拦截该事件,然后事件就会交给ViewGroup处理,然后它的onTouchEvent方法就会被调用,如果返回false表示它不拦截当前事件,这时事件就会传递给它的子元素,接着调用子元素的dispatchTouchEvent方法,如此反复直到事件被处理。

监听事件优先级OnTouchListener > OnTouchEvent > OnClickListener

当产生点击事件后,总是先传递给Activity,再传递给Window,最后传递给顶级View,然后按照事件的传递规则去分发事件。如果所有View都不处理这个事件,那么这个事件最终会传递给Activity处理,Activity的onTouchEvent方法会被调用。

总结

a.      同一个时间序列是指从手指接触屏幕开始,到手指离开屏幕,中间有一系列的move事件

b.     一般一个事件序列只能被一个View拦截消耗,因为一旦View拦截了某事件,那么同一事件序列内的所有事件都会直接交给它处理,但是View可以将本应自己处理的事件通过onTouchEvent强行传递给其它View处理。

c.      某个View一旦拦截那么该事件序列都只能由它来处理,并且它的onInterceptTouchEvent不会被调用。即不会再被拦截。

d.     某个View一旦开始处理事件,如果不消耗ACTION_DOWN事件,即onTouchEvent返回了false,那么同一事件序列中的其它事件不会再交给它处理,而是由它的父元素处理,即父元素的onTouchEvent会被调用。

e.    某个View不消耗除ACTION_DOWN以外的其它事件,那么这个点击事件会消失,但父元素的onTouchEvent不会被调用,并且当前View会持续收到后续事件,最终传递给Activity处理。

f.       ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false。

g.      View没有onInterceptTouchEvent方法,一旦事件传递给它,那么它的onTouchEvent方法就会被调用。

h.     View的onTouchEvent方法默认都是消耗事件,除非它是不可点击的(clickable和longClickable都为false,View的longClickable默认为false)。

i.       事件的传递过程是由外向内的,总是由父元素开始,再传递到子View,子View可以通过requestDisallowInterceptTouchEvent干预事件的分发,但是ACTION_DOWN除外。

2.     源码解析

1.     Activity的分发过程

当点击操作发生时,事件最先传递给当前的Activity,由Activity的dispatchTouchEvent进行事件派发,具体是由Activity内的Window来完成的,Window会将事件传递给decor view(setContentView设置的View的父容器)。

 

2.     Window是如何将事件传递给ViewGroup的

Window是个抽象类,Window的superDispatchTouchEvent方法也是个抽象方法,PhoneWindow是Window的唯一实现类。


PhoneWindow直接将事件传给了DecorView,DecorView是setContentView所设置View的父容器,所以事件肯定会传递给setContentView的View,也叫顶级View。

3.     顶级View对事件的分发过程

对于根ViewGroup来说,点击事件首先会传给它,此时调用它的dispatchTouchEvent方法,然后执行onInterceptTouchEvent 方法,如果该方法返回true,表示它要拦截该事件,然后事件就会交给ViewGroup处理,然后它的onTouchEvent 方法就会被调用,如果返回false表示它不拦截当前事件,这时事件就会传递给它的子元素,接着调用子元素的dispatchTouchEvent方法,如此反复直到事件被处理。

这个方法较长,我们分段来看

 

当ViewGroup的子元素成功处理时,mFirstTouchTarget会被赋值,并指向子元素,即当ViewGroup不拦截事件并交给子元素处理时mFirstTouchTarget != null。

当ViewGroup拦截时,mFirstTouchTarget就为空,当后续事件到来时actionMasked != MotionEvent.ACTION_DOWN并且mFirstTouchTarget== null,所以该条件为false,所以ViewGroup的onInterceptTouchEvent不会再被调用,并且同一事件序列中的其它事件都将交给它来处理。

特殊情况 : 标记位FLAG_DISALLOW_INTERCEPT,子View可以通过设置requestDisallowInterceptTouchEvent方法,使得ViewGroup无法拦截除了ACTION_DOWN以外的其它事件。如果是ACTION_DOWN事件,这个标记位就会被重置。

 

上面代码说明了ViewGroup会在ACTION_DOWN事件到来之前重置标记位的状态。当ViewGroup不再拦截事件的时候,事件就会下发至它的子View处理。源码如下所示

  

遍历ViewGroup所有子元素,判断子元素是否能够接收点击事件并且点击事件是否落在子元素的区域内。如果都满足,那么事件就交给它来处理,在dispatchTransformedTouchEvent方法中有这样一段代码。

 

如果子元素不为空,直接调用它的dispatchTouchEvent方法,如果返回true,那么mFirstTouchEvent会被赋值并且跳出循环。如果返回false,ViewGroup会把事件分发给下一个元素,

 

如果遍历完所有的元素后时间都没有被处理,那么有两种情况,一是ViewGroup没有子元素,二是子元素处理了点击事件,但是dispatchTouchEvent返回了false,一般是因为在TouchEvent中返回了false,这两种情况下ViewGroup会自己处理点击事件。

 

这里dispatchTransformedTouchEvent方法中第三个参数为null,由之前的代码分析,它会调用super.dispatchTouchEvent方法,即交给View处理,接着看View的处理。

4.     View对点击事件的处理过程。

 

首先判断有没有OnTouchListener,如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent就不会被调用,所以OnTouchListener优先级高于onTouchEvent。

接下来分析onTouchEvent,这里面主要分三个过程 : View处于不可用状态,View设置有TouchDelegate,View处于可用状态。

a.      View不可用

 

View在不可用状态下依然会消耗点击事件

b.     View设置了TouchDelegate

TouchDelegate可以让某个控件处理比它实际占用空间更大的触摸消息。具体可以参考官方文档 : TouchDelegate,具体使用 : TouchDelegate的使用

c.      View可用

整个过程分为ACTION_UP,ACTION_DOWN,ACTION_CANCEL,ACTION_MOVE四个状态,我们就最有代表性的ACTION_UP来分析

 

只要View的CLICKABLE或LONG_CLICKABLE为true,就会消耗这个事件,并返回true,然后触发performClick方法,如果View设置了onClick事件,那么onClick就会被调用。

 

可点击的View的CLICKABLE为true,不可点击的为false

View的LONG_CLICKABLE默认为false

setClickable和setLongClickable可以分别改变其属性。

setOnClickListener和setOnLongClickListener可以分别将CLICKABLE和LONG_CLICKABLE改为true。


到这里View的参数概念、滑动、事件分发就全部分析完了,以上所有内容选自Android开发艺术探索,感谢作者的辛勤付出,希望这些内容能对大家能有所帮助。