以前没太注意,很久没用之后再使用发现有些地方模糊了,就是那种不知道是对是错的感觉,然后又要重复上网去找资料,所以打算自己整理一篇,有很多时候,一些特殊的需要要是能巧妙的运用事件分发机制其实能很快的去解决问题。
一.流程
1.打印全流程
对于activity,viewgroup和view来说,如果不再任意一个流程消费事件,就会打印出这个结果。
这个就不用多解释了,如果都没消费事件,会在最后一句打印出ACTION_DOWN没有被处理。
2.图解过程
由上面打印的过程可以做出下面一张事件分发时的流程图:
但是在activity,viewgroup和view的dispatchTouchEvent、onTouchEvent这些方法中,返回值是一个布尔类型的,有三种情况,false,true和super,分别对这三种情况和分发流程进行探究后得到下图:
图太麻烦了,我就不重新画了,从网上找了一张,不同的是,图中的onTouchEvent,我试过,如果传的是super,是会被消费的,而不是返回上一层。
3.一般情况下的事件分发
上面的情况我是重写viewgroup重写view去重写onTouchEvent和dispatchTouchEvent方法,但是实际操作中不会总是这种情况,因为我们不可能把接触到事件分发需求的控件都重写,那样就太麻烦了。所以先来看看一般情况下的分发情况。
我把自定义view换成普通的view然后写onClick方法,打印以下结果
发现在onClick事件中,view会消费事件,即便你在onClick中没做什么操作,事件也会被view给消费,那不写onClick方法呢?
发现打印结果中,即便没对View监听,事件也不会往上传,然后我打算看看view中的源码
我* , 看来我本地是看不了了,别的地方找找。如果你看到了源码,你会发现卧槽真尼玛多,一大堆判断。我就找来了一个别人整理过的所写版的。感谢这位大神,很有良心。
if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
...
break;
...
}
return true;
}
看得出默认的情况下,返回true也就是被消费。我看了源码就知道了,当然没贴出来,这里设置setClickable的话就会有神奇的效果,我设置view.setClickable(false);
打印出下面的结果。
所以能得出一个简单的结论,一般情况下你不从写view,要让这个view的事件往上层分发,需要设置setClickable(false)
那有的朋友说,我要有那种点击view之后,view先做操作,然后viewgroup在做操作,而且还不是自定义view和viewgrou的条件下。如果是直接对View设置onClickListener的话是无法达到这个效果的,所以只能对view设置OnTouchListener
btnContent.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
Log.v("mmp", "view->setOnTouchListener");
}
return false;
}
});
这样就能先执行view的点击事件,再执行viewgroup的点击事件。
4.总结
事件分发有意思的地方就在于,你想让什么去触发这个事件,并且是否分发给上一层去做处理,更有意思的地方在于这个机制是先向下分发,由activity分发给viewgroup再分发给view,之后执行是向上执行,先由view执行。所以它使用起来会很灵活,比如说你想做一系列的点击事件,点击一个按钮后activity先执行某步操作,view再执行某步操作,然后activity再执行某部操作,这个做法也是可以做到的。
二.事件分发的灵活用法
事件分发他是一个机制,所以它可以适用于很多的场景,不要说它只能用于处理特殊的点击事件,那可真是暴殄天物。
1.防止快速点击
我们可以用事件分发机制来防止快速点击,如果对一个按钮就行快速点击那就会出现很糟糕的后果,所以一般的app中又要做防止快速点击的操作,有些人对一些按钮重复进行快速点击的操作,那就很浪费时间,可以直接在activity中做处理。
重写activity的dispatchTouchEvent方法,记录最后一次触发点击事件的事件,每次点击都获取时间,如果两个时间相减小于XXX秒,就返回true,这样快速点击的时候事件就不会分发下去,大于这个时间就返回super。
在activity中写
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
if (System.currentTimeMillis() - lastTime < 500) {
return true;
}
}
return super.dispatchTouchEvent(ev);
}
当然除了这样子做之外,你也可以自己写个根布局,然后所有xml中的布局都写这个根布局,再用viewgroup的onInterceptTouchEvent来拦截也行。
2.仿dialog点击外部内容消失效果
我也是因为这个需求所以才想写这个文章大,假如我要做一个图层,实现在recyclerview的item中弹出的效果,对item的弹框效果的图层,如果你是用一种图层的思想你就知道这个图层应该是做在item上,activity下,所以无法使用dialog或popupwindow,因为这两个弹框都是顶层的图层,所以只能加一层布局来显示和隐藏达到效果。而要实现这个效果,可以在activity的dispatchTouchEvent中加判断。