在安卓开发的时候 我们点击一个按钮 一般会做出相应事件,我们感觉理所当然 ,但是为毛这样呢 为啥它会响应 它怎么知道我点击了它呢,好了,那么今天我们就进行学习一下(分析源码哦)
一、什么是事件
首先要了解 当我们点击屏幕的时候 系统会把我们点击的这个动作封装成一个点击事件MotionEvent 。
- ACTION_DOWN:手指刚接触屏幕 (只有一个)
- ACTION_MOVE:手指在屏幕上滑动(0个或n个)
- ACTION_UP:手指从屏幕上松开的一瞬间(只有一个,cancel情况下可能没有)
好了上面就是我们的MotionEvent 简述,约简单越好 毕竟主攻事件分发呐亲 ~
二、事件分发谁来分发、需要哪些方法
2、事件分发 其实说到就是点击事件 Activity -->GroupView-->View 的事件分发。既然是分发肯定有一系列的方法,那么下面请大家记住这个三个关键方法!
- dispatchTouchEvent:进行事件分发的方法(activity groupView View 都有该函数)
- onInterceptTouchEvent:事件拦截的方法(groupView 才有哦 为啥呢,个人感觉 activity没有 是因为它本来就是分发的,还拦截干嘛,view 已经是分发的底层了,更不需要拦截了)
- onTouchEvent:进行事件的处理(activity groupView View 都有该函数)
三、源码分析
1、activity的事件分发
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); //标记1
}
if (getWindow().superDispatchTouchEvent(ev)) //标记2 {
return true;
}
return onTouchEvent(ev); //标记3
}
...标记1:我们点开看看:
public void onUserInteraction() {}
咦,空方法,其实这是实现屏保的地方,和我们今天要说的事件分发关系不大 那好吧继续往下看
...标记2:getWindow().superDispatchTouchEvent(ev),
getWindow()是获取了Window对象,
public Window getWindow() {
return mWindow;
}
我们看到MotionEvent 传递到superDispatchTouchEvent()这个方法里面了 那我们点开看看,(进入
Window类)
public abstract boolean superDispatchTouchEvent(MotionEvent event);
哎,咋是一个抽象方法啊 这可咋办 这事件传递到哪了,别慌 既然是抽象方法 肯定要实现的地方嗯
Window唯一的实现类是PhoneWindow
好进入PhoneWindow
找到实现方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
卧槽又调用了 ,继续进入
进入DecorView类
找到实现方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
。。。super 调用父类 那我们就看看DecorView的父类是 FrameLayout 咦那它的父类不是ViewGroup吗,
难道这就是activity传递事件到ViewGroup的交界处是的,嘿嘿,此时activity 事件分发结束
此时看到标记2当 getWindow().superDispatchTouchEvent(ev) 为真的情况下(其实也就是事件向下传递的时候有控件消费了事件
返回true) activity的dispatchTouchEvent方法返回true
不然就进行标记3,来看看标记3
...标记3:onTouchEvent(ev) activity消费事件的方法
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
无论onTouchEvent 返回什么事件都将被消费 至此activity的事件分发和事件消费结束
2、ViewGroup的事件分发
ViewGrop 的dispatchTouchEvent 中的代码太多 我主要列出一部分代码 分发逻辑不变
TouchTarget mFirstTouchTarget=null//标记1
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;//标记2
final boolean intercepted; //标记3
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev); //标记4
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;
}
if (!canceled && !intercepted) {//标记5
for (int i = childrenCount - 1; i >= 0; i--) { //标记6
if (!canViewReceivePointerEvents(child) //标记7
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//标记8
mFirstTouchTarget=被赋值
}
}
}
if(mFirstTouchTarget==null){
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
}else{
if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {
handled = true;
}
}
}
...标记1:mFirstTouchTarget 当前是否有子view消费事件的标记
...标记2:dispatchTouchEvent 的返回给activity的值 ture 代表事件被消费 flase 事件没消费
...标记3:intercepted 是否拦截事件的标记
...标记4:onInterceptTouchEvent 咦 好熟悉 这不是ViewGroup特殊的拦截方法吗
一般情况下都是默认返回 false 代表不拦截 当我们重写onInterceptTouchEvent 时返回true
那么代码ViewGroup拦截事件 子view就不被分发事件
...标记5:判断ViewGroup是否拦截事件
...标记6:如果不拦截的情况下进入 这个时候我们遍历子view
...标记7: // 条件1:canViewReceivePointerEvents,当前child是否能够接收到点击事件
// 条件2:isTransformedTouchPointInView,点击事件的坐标是否落在当前child的区域内
// 因此,当前child无法接收到事件或者点击事件不在当前child的区域内,就跳过,继续遍历下一个child
...标记8:调用 dispatchTransformedTouchEvent()方法,进行事件分发判断
那么现在开始看dispatchTransformedTouchEvent()方法的部分源码
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {
final boolean handled; //标记 9
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) { //标记10
handled = super.dispatchTouchEvent(event);//标记11
} else {
handled = child.dispatchTouchEvent(event);//标记12
}
event.setAction(oldAction);
return handled;
}
return handled;//标记13
}
...标记9:是否消费的返回值
...标记10:child == null 代表拦截事件
...标记11:调用 super. dispatchTouchEvent()
咦super 调用父类 那不是view吗对这个 然后会调用自己的onTouchEvent方法 这个先记住 牵扯到view层的事件分发
...标记12:调用子view的dispatchTouchEvent()好了马上进入view的dispatchTouchEvent()
...标记13:返回是否被消费的结果
ok ViewGroup的事件分发结束
2、View的事件分发
View的事件分发简单的多 作为事件分发的结束来一起看看吧 部分代码~
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;//标记1
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {//标记2
result = true;
}
if (!result && onTouchEvent(event)) {//标记3
result = true;
}
return result;
}
...标记1:是否消费事件的标记
...标记2:这个重点说一下 大家应该给某一个控件设置过onTouch事件 也设置过点击事件 当你onTouch中返回true时
这个时候你的点击事件就会失效 为啥呢来看看 li.mOnTouchListener.onTouch(this, event) 当他为真也就是返回
true的时候 我们的view dispatchTouchEvent 返回ture 结束 根本运行不到onTouchEvent 所以点击事件会失效。
同样view不可点击 view dispatchTouchEvent 返回ture
这个时候我们可以知道onTocuch事件早于onTouchEvent
by the way imageview默认不可点击哦
...标记3:顺利进入onTouchEvent方法了 我们进去看看
public boolean onTouchEvent(MotionEvent event) {
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {//标记3
switch (action) {
case MotionEvent.ACTION_UP:
performClickInternal();//标记4
}
return false;
}
...标记3:点击到了该view
...标记4:手送开手 进入performClick()
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this); //我们熟悉的onClick方法
result = true; //注册了点击事件 消费true
} else {
result = false;//不消费 false
}
return result;
}
ok View的事件分发也结束
四、结尾:
补充:onTouch 的各种响应事件都早于onClick方法哦 记住它永远比onClick先执行就对了
大概讲述了安卓的事件分发的整个过程 事件传递的过程 大致就是Activity -- >ViewGroup -- >View 的事件传递 其中ViewGroup 可以用onInterceptTouchEvent 方法进行事件拦截 ,ok就这样 事件分发大致就这样 ,如果有什么错误的地方或者不懂的地方麻烦留言 拜~