Activity的构成
Activity 在加载布局时,会先创建一个 Window 对象,这个对象是由 PhoneWindow 来实现的。PhoneWindow 又会创建一个 DecorView 作为整个窗口的根 View,而这个根 View 又会包含一个 LinearLayout,其中包含了 TitleView 和 ContentView,而我们后面的所写的应用的布局,正是在这个 ContentView 中。如下图所示:
触摸事件的类型
触摸事件对应的是 MotionEvent 类,事件类型主要有三种:
- ACTION_DOWN:用户按下操作,表示一次触摸事件的开始。
- ACTION_MOVE:在按下的情况下,进行移动。轻微的移动都会传递到该事件。
- ACTION_UP:用户手指离开屏幕,表示一次触摸事件的
注 :如果用户仅仅的是点击而已,则只会执行到 ACTION_DOWN 和 ACTION_UP 两个事件,不会执行到 ACTION_MOVE 事件。所以 ACTION_DOWN 和 ACTION_UP 是事件是必须的。
触摸事件的传递阶段
-
分发(Dispatch)
在Android系统中所有的触摸事件都是由 dispatchTouchEvent 方法进行分发的。方法返回值为true表示事件被当前视图消费掉,返回为super.dispatchTouchEvent表示继续分发该事件。如果当前视图是ViewGroup或者其子类,则会调用onInterceptTouchEvent 判断是否截拦。
-
截拦(Intercept)
事件的拦截onInterceptTouchEvent只存在于ViewGroup及其子类。方法返回值为true表示拦截这个事件并交由自身的onTouchEvent方法进行消费;返回false表示不拦截,需要继续传递给子视图。
如果return super.onInterceptTouchEvent(ev), 事件拦截分两种情况:-
如果该View(ViewGroup)存在子View且点击到了该子View, 则不拦截, 继续分发 给子View 处理, 此时相当于return false。
-
如果该View(ViewGroup)没有子View或者有子View但是没有点击中子View(此时ViewGroup 相当于普通View), 则交由该View的onTouchEvent响应,此时相当于return true。 注意:一般的LinearLayout、 RelativeLayout、FrameLayout等ViewGroup默认不拦截, 而ScrollView、ListView等ViewGroup则可能拦截,得看具体情况。
-
-
消费
方法返回值为true表示当前视图可以处理对应的事件;返回值为false表示当前视图不处理这个事件,它会被传递给父视图的onTouchEvent方法进行处理。如果return super.onTouchEvent(ev),事件处理分为两种情况:
- 如果该View是clickable或者longclickable的,则会返回true, 表示消费 了该事件, 与返回true一样;
- 如果该View不是clickable或者longclickable的,则会返回false, 表示不 消费该事件,将会向上传递,与返回false一样.
所有拥有事件传递能力的类:
- Activity: 拥有dispatchTouchEvent 、OnTouchEvent
- ViewGroup:拥有dispatchTouchEvent 、OnInterceptTouchEvent 、OnTouchEvent
- View:拥有dispatchTouchEvent 、OnTouchEvent
点击事件分发
- ACTION_DOWN事件触发时,会从Activity -> PhoneWindow -> DecorView -> ViewGroup -> View顺序,逐层调用对应的dispatchTouchEvent方法分发。
- 在View的dispatchTouchEvent中,常理会调用onTouchEvent,在onTouchEvent方法中,ACTION_DOWN时,会检查是否是长按,时长超过500毫秒则是长按;在ACTION_UP时,会调用performOnClickInteral,依次调用onClick(),响应点击事件。如果重写onTouch()方法,返回true,则View的dispatchTouchEvent中,有一个if判断条件成立,会将boolean局部变量result置true,那么接下来一个if判断中,该result为true,就不会调用onTouchEvent方法,因此该次触摸事件则消费结束,不会再响应onLongClick和onClick。如果重写onLongClick()方法,返回true,则在onTouchEvent方法中,ACTION_DOWN时,检查是长按后,会调用onLongClick(),响应长按事件,该方法返回true后,会将mHasPerformedLongPress代表是否执行了长按事件的boolean值置true;在ACTION_UP时,调用performOnClickInteral前,会先判断mHasPerformedLongPress,为true,则不调,为false,才调用。因此,重写onLongClick()方法,返回true时,不会再响应onClick。
点击事件传递过程:
- ACTION_DOWN事件,Viewgroup的dispatchTouchEvent分发事件,onInterceptTouchEvent拦截事件,默认不拦截,继续向下分发给子View,子View的dispatchTouchEvent分发事件,传给子View的onTouchEvent,若返回true消费,则后面的ACTION_MOVE和ACTION_UP事件按顺序继续分发传递。
- 事件传给子View的onTouchEvent时,子View返回false,不消费事件,则事件回传给ViewGroup的onTouchEvent来消费,以后的时间会按照ViewGoup的dispatchTouchEvent到onTouchEvent来传递消费。
- 当ViewGroup的onInterceptTouchEvent拦截事件后,则会传递给ViewGroup的onTouchEvent消费,以后的其他事件也会按照ViewGoup的dispatchTouchEvent到onTouchEvent来传递消费,不会再传给子View。
解决触摸事件冲突:
- 外部拦截。ViewGroup重写onInterceptTouchEvent方法,默认不拦截,事件往下分发给子View,若返回true,则拦截此次事件,将事件传给ViewGroup的onTouchEvent处理。
- 内部拦截。重写子View的dispatchTouchEvent方法,方法中调用getParent().requestDisallowInterceptTouchEvent(true)方法,传true则代表不希望ViewGroup拦截事件,传false则代表希望ViewGroup拦截事件。
- 内部拦截。子View重写onTouchEvent方法,返回true,则子View消费该次事件,返回false,该次事件返回给ViewGroup的onTouchEvent处理。
总结
-
onInterceptTouchEvent
返回值true表示事件拦截,onTouch/onTouchEvent
返回值true表示事件消费。 -
触摸事件先交由
Activity.dispatchTouchEvent
。再一层层往下分发,当中间的ViewGroup都不拦截时,进入最底层的View后,开始由最底层的OnTouchEvent
来处理,如果一直不消费,则最后返回到Activity.OnTouchEvent
。 -
ViewGroup才有
onInterceptTouchEvent
拦截方法。在分发过程中,中间任何一层ViewGroup都可以直接拦截,则不再往下分发,而是交由发生拦截操作的ViewGroup的OnTouchEvent
来处理。 -
子View可调用
requestDisallowInterceptTouchEvent
方法,来设置disallowIntercept=true
,从而阻止父ViewGroup的onInterceptTouchEvent
拦截操作。 -
OnTouchEvent由下往上冒泡时,当中间任何一层的OnTouchEvent消费该事件,则不再往上传递,表示事件已处理。
-
如果View没有消费ACTION_DOWN事件,则之后的ACTION_MOVE等事件都不会再接收。
-
只要
View.onTouchEvent
是可点击或可长按,则消费该事件. -
onTouch
优先于onTouchEvent
执行,onTouch
的位置在onTouchEvent
前面。当onTouch
返回true,则不执行onTouchEvent
,否则会执行onTouchEvent。onTouch
只有View设置了OnTouchListener
,且是enable的才执行该方法。