Android 事件分发一直都是一个难点,让人很模糊,感觉自己知道点,但又不是很清楚。最重要的是我知道了理论,怎么没感觉在实际开发中用到呢?今天决定来好好研究一下这个事件分发机制,如有不正确的地方,请多多指教。
首先看上图,我们要知道这个层次关系,你可以把每一层理解为一张纸放在桌子上,那就是Activity这张纸在最下面,ViewGroup在Activity的上面,View在ViewGroup的上面。
好了,理解完层次关系,我们可以点一下View内任意一点。这时View是接受了一个事件(先不用管是什么事件,随便你就理解为一个ACTION_DOWN事件)。到这里我要说下我原来的错误认识,做个反面教程,让大家更好的理解。我原来认为,View最先接收到到事件,所以事件的传递肯定是View---ViewGroup---Activity这样由外往里传递的。
其实不然,你可以这样理解。Activity、ViewGroup、View是一个部门的三个同事,只不过Activity是部门主任,ViewGroup是主管,View是一个职员。
突然有一天View接受到一个任务(触摸事件),那我们要想一下这个任务是怎么来的呢,无论这个任务是你自己部门内部的,还是兄弟部门的协作任务。只要给你分配任务,那都要必须经过你的主任和主管的同意,因为他们是你的顶头上司,而你是他们的干将。所以说这个任务也就是这个事件,在传到View之前,早已经经过了你的领导的同意,也就是早就经过了Activity和ViewGroup。如果领导不同意,或是半路领导就一句话的事帮你把事给解决了。那这个任务也就不会传递到View这来了。
总结:虽然我们是对View发起了触摸事件,但是事件的传递开始于Activity。由Activiy----ViewGroup---View。
好了,到这里大体知道了事件传递流程了。但是我们还要知道几个重要的方法。如下图所示,除了ViewGroup多了一个onInterceptTouchEvent方法外,他们都有dispatchTouchEvent和onTouchEvent。
接下来我们看看这几个方法都是什么意思,还是说下我原来的认识误区。我原来认为dispatchTouchEvent是事件分发的方法,所以它返回true代表向下分发事件,false代表不向下分发,如果return super. dispatchTouchEvent(),我就认为交给父类处理。
我当时的认知是不完全正确的,那说下这几个方法分别返回 true或false或是return super.xxx到底是什么意思。
- dispatchTouchEvent
true:代表消费掉事件,不再向下传递
false:代表不向下分发事件,不处理,再把事件返还到上一层
super:表示继续向下传递 - onInterceptTouchEvent
true: 代表拦截事件,不向下传递,交给自己的onTouchEvent处理
false和super:都表示不拦截,继续向下传递事件 - onTouchEvent:
true:表示消费掉事件
false:Activity中表示丢弃事件不处理,而如果是在ViewGroup和View中表示自己不处理,交给上级
super:交给上级处理
这里还要区分是谁的dispatchTouchEvent方法,Activity和View的这个方法同样的返回值,所做的操作是有区别的。详细可以看下面的流程图
是时候展示真正的技术了:我看了好多类似的事件分发流程图,乍眼一看都有点蒙圈,觉得好凌乱复杂。所以我也画了一个图,个人认为还算清晰简洁。
你不需要太仔细看这个图,先简单的看下。然后就是需要自己撸代码验证的时刻了,这时会加深你的理解。撸代码回来再仔细看,你就会恍然大明白了。
那怎样写代码,很简单:
- 先自定义一个ViewGroup继承LinearLayout,重写需要的几个方法
- 自定义View继承TextView,重写需要的方法
- 在Activity的布局代码中加入你自定义的ViewGroup,ViewGroup里面是的自定义的TextView。Activity中重写需要的方法。
//自定义ViewGroup
public class MyViewGroup extends LinearLayout {
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/*************下面是我们需要的方法*************/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("ViewGroup", "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("ViewGroup", "onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("ViewGroup", "onTouchEvent");
return super.onTouchEvent(event);
}
}
//自定义TextView
public class MyTextView extends android.support.v7.widget.AppCompatTextView {
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/*************下面是我们需要的方法*************/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("View", "dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("View", "onTouchEvent");
return super.onTouchEvent(event);
}
}
//Activity中的代码
public class Main6Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main6);
}
// 这里只打印ACTION_DOWN的传递,不然会有移动和抬起的事件,在一起比较乱
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction()==MotionEvent.ACTION_DOWN){
Log.e("Activity", "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("Activity", "onTouchEvent");
return super.onTouchEvent(event);
}
}
ok,到这里准备工作就完事了,非常简单的代码。运行程序后,点下View看看打印结果。然后再分不同的情况,分别更改返回的值为true或false或是super.xxx。分别看看打印情况,你就会知道了传递流程了。