Android的事件分发机制
主要方法:dispathTouchEvent(事件分发) onInterceptTouchEvent(事件拦截) onTouchEvent(事件处理)
PS:Activity和View只有分发和处理两个方法,只有ViewGroup有三个方法,多一个拦截方法。
(1)onTouch是优先于onClick执行,事件传递的顺序是先经过onTouch,再传递到onClick。
(2)Android的事件响应机制是“由外到内”分发,“由内到外”处理的形式实现的。
(3)点击事件产生后的传递顺序:activity-window-viewGroup-view,若所有元素都未处理则顺序相反。(即:回传机制)
1.事件传递的方向:父控件→子控件
2.事件响应的方向:子控件→父控件
(4)事件的处理:冒泡式消费
只执行onTouchEvent方法(view没有拦截方法,事件传递过来时一定会执行这个方法,当clickable和longclickable都为true时如button默认消费事件)
true,处理了,不用处理了
false,没处理,给上级处理
流程图:
实例代码+解析:
1.ViewGroupA
package com.yhy.touchevent;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
/**
* Created by ${yinhaiyang} on 2018/8/1.
*/
public class ViewGroupA extends LinearLayout {
public ViewGroupA(Context context) {
super(context);
Log.i("OnTouchEvent", "ViewGroupA==+1参构造");
}
public ViewGroupA(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
Log.i("OnTouchEvent", "ViewGroupA==+2参构造");
}
public ViewGroupA(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.i("OnTouchEvent", "ViewGroupA==+3参构造");
}
/**
* 分发
*
* @param ev
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
ActionUtiles.ProcessEvent(ev,"ViewGroupA+分发+dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
// return true;
}
/**
* 拦截
*
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ActionUtiles.ProcessEvent(ev,"ViewGroupA+拦截+onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
/**
* 处理
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
ActionUtiles.ProcessEvent(event,"ViewGroupA+处理+onTouchEvent");
return super.onTouchEvent(event);
// return true;
}
}
2.ViewGroupB
package com.yhy.touchevent;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
/**
* Created by ${yinhaiyang} on 2018/8/1.
* PS:事件不往下分发了,意味着后面的方法不会被执行了。
* 默认值情况下,“被点击的控件”无论是view还是ViewGroup,它的onTouchEvent方法都会被执行。
* 总结:
* (前体条件,未经特殊设置过的方法全部返回的是默认值情况。)
* ViewGroup的dispathceptTouchEvent()方法,return true,事件分发拦截,事件回传拦截,不往下(子控件)(不分发)、不往上传递(不回传)。
* return false,事件拦截,事件回传,自己不处理,回传给父控件的onTouchEevent()来处理。
* return super.dispathceptTouchEvent(ev);默认值,事件出给自己的onTouchEvent(),看其是否处理,若返回true(处理),则回自己的onTouchEvent()消费掉事件(不回传),
* 否则返回false或者默认值(不处理),继续回传,回传给父控件onTouchEvent()来处理,若都不处理,直至传到最外层消失。
* onInterceptTouchEvent返回true时:问自己的onInterceptTouchEvent(),是否拦截。
* return true(拦截),由自己的onTouchEvent消费(不回传)
* return false或者是默认值(不拦截),事件继续传递,且回传,回传给父控件onTouchEvent()来处理,若都不处理,直至传到最外层消失。
*/
public class ViewGroupB extends LinearLayout {
public ViewGroupB(Context context) {
super(context);
Log.i("OnTouchEvent", "ViewGroupB==+1参构造");
}
public ViewGroupB(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
Log.i("OnTouchEvent", "ViewGroupB==+2参构造");
}
public ViewGroupB(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.i("OnTouchEvent", "ViewGroupB==+3参构造");
}
/**
* 分发
*
* @param ev
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
ActionUtiles.ProcessEvent(ev,"ViewGroupB+分发+dispatchTouchEvent");
return super.dispatchTouchEvent(ev);//返回的是默认值情况,事件继续分发,自己的onInterceptOntouchEvent()会被执行。
// return true;//dispatchTouchEvent()返回true,事件被拦截,不会往下传递也不会回传,事件在此处消失。
// return false;//dispatchTouchEvent()返回false,表示事件不做分发,自己oninterceptTouchEvent()方法不会执行,所以它的onTouchEvent方法也不会被执行,事件会回传给父控件的OnTouchEvent方法处理,如果父控件返回的是默认值则一直往上传递,最终事件消失。PS:回传的过程中如果有OntouchEvent方法返回true的,则事件被处理,事件就也此终止。
}
/**
* 拦截
*
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ActionUtiles.ProcessEvent(ev,"ViewGroupB+拦截+onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);//返回的默认值情况,事件继续分发,如果点击的是自己,那么自己的onTouchEvent()方法会被执行,如果点击的是子View,事件继续传递,回传机制。
// return true;//onInterceptTouchEvent()返回true,表示拦截事件,则事件交给自己的onTouchEvent方法处理,看onTouchEvent是否处理了,如果不处理继续执行回传机制。
// return false;//onInterceptTouchEvent()返回false,效果和默认值情况相同。
}
/**
* 处理
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
ActionUtiles.ProcessEvent(event,"ViewGroupB+处理+onTouchEvent");
return super.onTouchEvent(event);//返回的是默认值。
//return false;//ViewGroup的onTouchEvent()方法返回false,和默认情况一样。
// return true;// ViewGroup的onTouchEvent()方法返回true,表示调用了我之后,我就做事件处理了。由于ViewGroup的OnInterceptTouchEvent返回值为默认的false,不会立即调用自己的OnTouchEvent处理事件。当我们按下的时候,事件还是继续分发给孩子组件,孩子组件没有处理事件,就回传给自己的onTouchEvent,看看是否处理事件,如果返回的是true,则处理事件,事件在此消失。否则的话继续向上级回传。UP事件则是由自己的onTouchEvent处理了。
}
}
3.MyView
package com.yhy.touchevent;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by ${yinhaiyang} on 2018/8/1.
* 总结:
* View没有子控件
* dispathTouchEvent(MotionEvent event)
* return true,不再分发事件,表示拦截事件拦截掉事件不会回传,事件在此处消失。
* return false,不做分发事件,事件被拦截,回传给父控件的处理方法。
* return super.dispathTouchEvent();默认返回值,根据事件传递的机制,此时会传给自己的OnTouchEvent方法。
*/
public class MyView extends View {
public MyView(Context context) {
super(context);
Log.i("OnTouchEvent", "MyView==+1参构造");
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
Log.i("OnTouchEvent", "MyView==+2参构造");
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.i("OnTouchEvent", "MyView==+3参构造");
}
/**
* 分发
* @param event
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
ActionUtiles.ProcessEvent(event,"MyView+分发+dispatchTouchEvent");
return super.dispatchTouchEvent(event);
// return false;//返回值为false,表示该View本身不做事件的分发了,所以自己后续的OnTouchEvent方法不会被执行了,直接回传给父控件的OnTouchEvent方法去处理。如果父控件的方法都是默认的情况,则一直传到Activity,事件消失。
// return true;//返回值为true,表示不再分发事件也可以说是拦截事件,此时该View的OnTouchEvent方法不会被调用,事件也不会回传。
}
/**
* 处理
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
ActionUtiles.ProcessEvent(event,"MyView+处理+onTouchEvent");
return super.onTouchEvent(event);
// return false;//返回false,说明事件并没有被消费掉,效果与默认值相同,事件会回传。
// return true;//返回true,说明事件被该View消费了,事件到此结束不再回传了。
}
}
4.布局main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<com.yhy.touchevent.ViewGroupA xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_red_dark">
<com.yhy.touchevent.ViewGroupB
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@android:color/holo_green_dark">
<com.yhy.touchevent.MyView
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@android:color/holo_blue_bright" />
</com.yhy.touchevent.ViewGroupB>
</com.yhy.touchevent.ViewGroupA>
5.效果图
简单总结:
dispathTouchEvent -->true:拦截,事件到此为止,不回传
-->false,不分发(即后面的控件则接不到任何事件了),回传机制
-->默认值,分发且回传
onInterceptTouchEvent-->true,拦截,不回传,调用自己onTouchEvvent方法处理,回传机制
-->false,-->默认值,两者中情况相同
(一旦onInterceptTouchEvent拦截了Down事件,那么后续的Move、Up事件则都由当前的ViewGroup来处理,不再向下传递。 )
onTouchEvent -->true,回传机制,若子控件都不处理,则传给自己的onTouchEvent方法处理,事件就此消失
-->false, -->默认值,两者情况相同
ps:该总结中没有涉及到onClick方法的情况。前提条件都是在没有特殊设置返回值的方法都返回默认值。
本文是自己学习后的总结笔记,详细讲解看:博客:https://blog.csdn.net/qq_32059827/article/details/52577017#commentBox
https://blog.csdn.net/m0_37700275/article/details/77947974
有onClick的情况,给三个控件都添加onclick监听事件。
(1)所有方法都设置默认值的情况下,点击那个控件默认后会由哪个控件处理该事件,无回传机制。(PS:这与前面总结的不同)。
(2)在这里要强调View的OnTouchListener。如果View设置了该监听,那么OnTouch()将会回调。 * 如果返回为true那么该View的OnTouchEvent将不会在执行 这是因为设置的OnTouchListener执行时的优先级要比onTouchEvent高。 * 优先级:OnTouchListener > onTouchEvent > onClickListener。