点击事件
**;
点击事件
**的**事件分发
**,本质是对**MotionEvent事件
**的**分发过程
**,**
即,
当一个**MotionEvent
**产生了以后,
系统需要把这个**事件
**传递给一个**具体的View
**,
而这个**传递的过程
**就是**分发过程
**。点击事件的分发过程
**由三个重要方法共同完成:**dispatchTouchEvent
**、**onInterceptTouchEvent
**和**onTouchEvent
**。**public boolean dispatchTouchEvent(MotionEvent ev)
当前View
**的**onTouchEvent
**
和**下级View
**的**dispatchTouchEvent
**方法的影响;public boolean onInterceptTouchEvent(MotionEvent event)
dispatchTouchEvent()
内部调用,用来判断是否拦截某个事件;拦截
**了某个事件,那么在**同一个事件序列
**当中,
此方法不会**被再次调用
**,是否拦截当前事件
**。public boolean onTouchEvent(MotionEvent event)
dispatchTouchEvent
**方法中调用,用来**处理点击事件
**;是否消耗当前事件
**,不消耗
**,则在**同一个事件序列
**中,
当前View无法**再次接收
**到事件。 public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
View层
**的**传递规则
**: 根ViewGroup
**来说,
点击事件**产生
**后,首先会**传递
**给它,
这时其**dispatchTouchEvent
**会被调用;onInterceptTouchEvent
**方法
返回**true
**就表示它**要拦截
**当前事件,
接着事件就会交给这个**ViewGroup
**处理,
即它的**onTouchEvent
**方法就会被调用;!!!onInterceptTouchEvent
**方法
返回**false
**就表示它不拦截当前事件,
这时当前事件就会**继续传递
**给它的**子元素
**,
接着**子元素
**的**dispatchTouchEvent
**方法就会**被调用
**,
如此反复直到事件被最终处理。onTouch()
**-->**onTouchEvent()
** -->**performClick()
** --> onClick()
**】!!!,**
否则继续往下传!这里可以看一下文末的两篇博客!
View
**需要**处理事件
**时,
如果它设置了**OnTouchListener
**,
则**OnTouchListener
**中的**onTouch
**方法会被回调;onTouch
**的**返回值
**,- **如果返回****`false`****,【事件不消费,继续往下传递】**
【注意这里跟**onInterceptTouchEvent
**不一样,
onInterceptTouchEvent
**仅在ViewGroup级,**
true表拦截处理,调用**ViewGroup
**自身的**onTouch()
**-->**onTouchEvent()
**,
onTouch
**在View级时候,**
false
**表**继续流程
**,调用**View
**自身的**onTouchEvent()
**】**
- **如果返回****`true`****,【事件被消费】**
优先级:**
onTouch()
**-->**onTouchEvent()
** -->**performClick()
** -->onClick()
以上是事件处理方法的优先级顺序,按照这个顺序, 只要**排在前面
**的**事件方法
**返回**true
**,**消耗处理
**了**点击事件
**了,点击事件
**便就地结束,不再下发,**排在后面
**的**点击事件
**也就不会再被调用和响应了;** 【文末有实例】另,onTouch()
的实现需要实现onTouchListener
;onTouchEvent()
/performClick()
直接在自定义View文件中重写即可;onClick()
的实现需要实现onClick
;
Activity -> Window -> 顶级View
**(上述说的表示View层中的顺序);
onTouchEvent
**返回**false
**,
那么它的父容器的**onTouchEvent
**将会被调用,
依此类推。
【除非下往上回传到某个返回true的onTouchEvent(),
则在那里停止,否则——】Activity
**处理,
即**Activity
**的**onTouchEvent
**方法会被调用。关于事件传递机制的一些结论(每一个点前面的短语是一个笔者自提的概况中心,便于记忆) 根据它们可以更好地理解整个传递机制: (1)【事件序列,定义】 “**
同一个事件序列
**” 的定义: 指从手指**接触
**屏幕的那一刻**起
**, 到手指**离开
**屏幕的那一刻**结束
**, 在这个过程中**所产生
**的一系列事件, 这个事件序列以**down事件
**开始, 中间含有**数量不定
**的**move
**事件, 最终以**up事件
**结束。
(2)【处理事件,独一无二】
正常情况下,**一个事件序列
**只能被**一个View
**拦截且**消耗
**!!!
这一条的原因可以参考(3),
因为一旦一个元素**拦截
**了某此事件,
那么**同一个事件序列内
**的**所有事件
**都会直接**交给它处理
**!!!
因此**同一个事件序列中
**的**事件
**不能分别由两个**View
**同时处理!!!
除非,
将本该由某个**View
**自己处理的事件
通过**onTouchEvent
**强行传递给**其他View
**处理。
(3)【事件序列,从一而终】
某个**View
**一旦决定**拦截
**,则这一个**事件序列
**都只能由它来处理
(如果事件序列能够传递给它的话),
并且它的**onInterceptTouchEvent
**不会再被调用!!!
当一个**View
**决定**拦截
**一个事件后,
那么系统会把**同一个事件序列内
**的**其他方法
**都直接交给它来处理,
因此
就不用再调用这个View的**onInterceptTouchEvent
**去询问它是否要拦截了。
(4)【短期失信】
某个**View
**一旦**开始处理事件
**,
如果它**不消耗ACTION_DOWN
**事件(**onTouchEvent
**返回了**false
**),
那么**同一事件序列中
**的**其他事件
**都不会再交给它来处理,
【即,**View
**放弃处理ACTION_DOWN,便放弃了**整个事件序列
**!!!】
并且事件将重新交由它的**父元素
**去处理,
即父元素的**onTouchEvent
**会被调用。【事件向上“回传”】
即,
事件一旦交给一个View处理,那么它就必须消耗掉!!!
否则**同一事件序列
**中剩下的事件就不再交给它来处理了!!!
好比上级交给程序员一件事,如果这件事没有处理好,
短期内上级就不敢再把事情交给这个程序员做。
(5)【余粮上缴】
如果View不消耗除ACTION_DOWN以外的其他事件,
那么这个点击事件会消失,
此时父元素的onTouchEvent并不会被调用,
并且当前View可以持续收到后续的事件,
最终这些**消失的点击事件
**会传递给**Activity
**处理。
(6)ViewGroup默认不拦截任何事件。
Android源码中
ViewGroup的**onInterceptTouch-Event
**方法默认返回**false
**。
(7)View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。
(8)View的**onTouchEvent
**默认都会**消耗事件
**(返回**true
**)!!!!!!!
除非它是**不可点击
**的(**clickable
** 和**longClickable
**同时为**false
**)。
View的**longClickable
**属性默认都为**false
**,
clickable
**属性要分情况,**
比如**Button
**的clickable属性默认为**true
**,
而**TextView
**的clickable属性默认为**false
**。
(9)【**enable
**无用,**clickable
**居上】
View的**enable属性
**不影响**onTouchEvent
**的**默认返回值
**。哪怕一个View是**disable
**状态的!!!!!
只要它的**clickable
**或者**longClickable
**有一个为**true
**,
那么它的**onTouchEvent
**就返回true!!!
(10)**onClick
**会发生的前提是**当前View
**是可点击的,并且它收到了**down
**和**up
**的事件。
(11)【由外而内;以下犯上】
事件传递过程是**由外向内
**的,
即事件总是先传递给**父元素
**,然后再由**父元素
**分发给**子View
**,
通过**requestDisallowInterceptTouchEvent
**方法可以在**子元素
**中**干预父元素
**的**事件分发
**过程,但是**ACTION_DOWN
**事件除外。
稍微复习一下: 事件方法的优先级:**
onTouch()
**-->**onTouchEvent()
** -->**performClick()
** -->onClick()
以上是事件处理方法的优先级顺序,按照这个顺序, 只要**排在前面
**的**事件方法
**返回**true
**,**消耗处理
**了**点击事件
**了,点击事件
**便就地结束,不再下发,**排在后面
**的**点击事件
**也就不会再被调用和响应了;** 下面是关于**事件优先级
**的一个实例:
public class DragView3 extends View implements View.OnClickListener {
private int lastX;
private int lastY;
public DragView3(Context context) {
super(context);
ininView();
}
public DragView3(Context context, AttributeSet attrs) {
super(context, attrs);
ininView();
}
public DragView3(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ininView();
}
private void ininView() {
setBackgroundColor(Color.BLUE);
this.setOnClickListener(this);//测试onTouchEvent与onClick的优先级!!
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
// LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
}
return true;
}
//测试onTouchEvent与onClick的优先级!!
@Override
public void onClick(View v) {
setBackgroundColor(Color.RED);
}
}
onClick监听器
**,
如果**onClick
**能**响应
**,点击View之后会从**蓝色
**变成**红色
**,
但是运行之后我们发现并没有变色,即**onClick
**没有被调用;
View响应的只是**onTouchEvent
**中的**滑动逻辑
**而已。(下面图一)onTouchEvent
**返回**true
**,把**事件消耗掉
**了!!
于是事件在**onTouchEvent
**中**处理结束
**,不再往下传,传不到**onClick
**那里!!!onTouchEvent
**注释掉,
使之默认返回**false
**,不消耗事件,这时**onClick
**则会响应!
那么再次运行程序,可以发现点击View之后,
View从蓝色变成红色!!!(下面图二)事件处理方法
**的**优先级
**不言而喻!图一
图二
dispatchTouchEvent
、onInterceptTouchEvent
和onTouchEvent
;分别的作用和关系;分发顺序
**的**从上往下
**的过程!!!!!
逻辑骨架就是,
接收到事件 --> 分发 --> 是否拦截
--> 拦截则就地处理【ViewGroup/View:调用自身**onTouch()
**-->**onTouchEvent()
** -->**performClick()
** --> onClick()
**】!!!,**
否则继续往下传,传到最下层的View为止,接着进入处理过程!
分发的顺序是**Activity -> Window(PhoneWindow) -> DecorView -> 顶级View(上述说的表示View层中的顺序) -> ViewGroup -> View
**;
这里可以看一下文末的两篇博客! 从下到上
**的过程,
从**最下层的View
**开始到**最上层
**(即**Activity
**),
如果所有元素都不消耗这个事件,事件最终就传回Activity;
消耗指onTouch、onTouchEvent、onClick等;Android
事件分发流程: Activity -> ViewGroup -> View;Activity
对点击事件的分发过程ViewGroup
对点击事件的分发过程View
对点击事件的分发过程点击事件
**用**MotionEvent
**来表示,**
当一个点击操作发生时,事件最先传递给当前Activity,
由Activity的dispatchTouchEvent来进行事件派发,
具体的工作是由**Activity内部
**的**Window
**来完成的!!!!!!!!Window
**会将事件传递给**decor view
**,**
decor view
**一般就是当前界面的底层容器(即**setContentView
**所设置的View的父容器),**
通过**Activity.getWindow.getDecorView()
**可以获得。 public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
如上,
首先事件开始交给**Activity
**所附属的**Window
**进行**分发
**,如果返回**true
**,
整个事件循环就结束了:
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
返回**false
**意味着事件没有元素处理,
所有View的**onTouchEvent
**都返回了false,
那么Activity的onTouchEvent就会被调用。
return onTouchEvent(ev);
Window
**是个**抽象类
**!!!**
而**Window
**的**superDispatchTouchEvent
**方法也是个**抽象方法
**!!!
因此我们必须找到**Window的实现类
**才行。源码:
public abstract boolean superDispatchTouchEvent(MotionEvent event);
PhoneWindow
**,
这一点从Window的源码中有这么一段话:Abstract base class for a top-level window look and behavior policy.
An instance of this class should be used as the top-level view added to
the window manager. It provides standard UI policies such as a background, title area,
default key processing, etc.
The only existing implementation of this abstract class is android. policy.
PhoneWindow,which you should instantiate when needing a Window.
Eventually that class will be refactored and a factory method added for creating
Window instances without knowing about a particular implementation.
- **`Window类`****可以控制****`顶级View`****的****`外观`****和****`行为策略`****!!!**
- **它的****`唯一实现`****位于****`android.policy.PhoneWindow`****中!!!**
- **当你要****`实例化`****这个****`Window类`****的时候,**
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
PhoneWindow
**将事件直接传递给了**DecorView
**!!!!!!!!!!**DecorView
**是什么:**private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
// This is the top-level view of the window,containing the window decor.
private DecorView mDecor;
@Override
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0)
**可以获取**Activity
**所设置的**View
**!!!!!!!!
这个**mDecor
**就是**getWindow().getDecorView()
**返回的**View
**!!!
而通过**setContentView
**设置的View是它(DecorView mDecor)的一个**子View
**【所谓**顶级View
**】!!!DecorView
**这儿,
由于**DecorView
**继承自**FrameLayout
**且是**父View
**,
所以最终事件会传递给**View
**!!!
从而**应用
**能**响应点击事件
**!!顶级View
**了,
即
在**Activity
**中通过**setContentView
**所设置的**View
**,
另外**顶级View
**也叫**根View
**,
顶级View
**一般都是**ViewGroup
**。**点击事件
**达到**顶级View
**(一般是一个ViewGroup)以后,**
会调用**ViewGroup
**的**dispatchTouchEvent
**方法,
然后,
如果**顶级ViewGroup
**拦截事件即**onInterceptTouchEvent
**返回true,
则事件由ViewGroup处理,
如果ViewGroup的mOnTouchListener被设置则**onTouch
**会被调用,
否则**onTouchEvent
**会被调用。
如果都提供的话,onTouch会屏蔽掉onTouchEvent。子View
**的**dispatchTouchEvent
**会被调用。
到此,事件已经从顶级View传递给了下一层View,接下来的传递过程和顶级View是一致的,如此循环,完成整个事件的分发。以上是对原理部分的回顾; 下面开始顶级View的源码分析;
ViewGroup的dispatchTouch-Event
方法中,
这个方法比较长,这里分段说明。首先下面一段,描述当前View是否拦截点击事情这个逻辑。
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
事件类型为ACTION_DOWN
**或者**mFirstTouchTarget != null
**。**
ACTION_DOWN事件好理解,那么mFirstTouchTarget != null是什么?子元素成功处理
**时,
mFirstTouchTarget
**会被赋值并**指向子元素
**【于是 != null】,**
换种方式来说,
当ViewGroup【**不拦截事件
**并将事件交**由子元素处理
**时
mFirstTouchTarget != null
**】。**
反过来,
一旦事件由当前ViewGroup**拦截
**时,
mFirstTouchTarget != null
**就不成立。**...
参考: