Android事件分发传递回传机制详解


转载本专栏每一篇博客请注明转载出处地址,尊重原创。此博客转载链接地址:点击打开链接   http://blog.csdn.net/qq_32059827/article/details/52577017

之前对这篇文章写的不够详细,觉得有必要在对这篇文章完善一下。本文使用Log方式分析,不涉及源码。log方式更直观易懂一些。

首先,View的几个基本的继承关系:


本博客案例的图层:

要实现上边这个图层结构,需要自定义View,并在里面加入log打印,只有自定义View才能测试log情况。我们这里自定义View只需要重写几个分发事件方法即可。如下:

MainActivity:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    
    /**
     * activity的
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
    	ActionUtiles.processEvent(event, "Activity+分发+dispatchTouchEvent");
    	return super.dispatchTouchEvent(event);
    }
    
    /**
     * activity的
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    	ActionUtiles.processEvent(event, "Activity+处理+onTouchEvent");
    	return super.onTouchEvent(event);
    }
}
由于Activity只有dispatchTouchEvent和onTouchEvent两个方法。因此重写之
然后,新建两个容器类型的View:

自定义View,第一个容器View----类型属于ViewGroup:(LinearLayout就属于容器类型的View,因此继承自它。继承其他容器View也可以比如RelativeLayout也可以的)

public class ViewGroupOne extends LinearLayout {

	public ViewGroupOne(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public ViewGroupOne(Context context) {
		super(context);
	}

	/**
	 * 分发事件
	 */
	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupOne+分发+dispatchTouchEvent");
		return super.dispatchTouchEvent(event);
		//return true;
		//return false;
	}
	
	/**
	 * 拦截事件
	 */
	@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupOne+拦截+onInterceptTouchEvent");
		return super.onInterceptTouchEvent(event);
//		return true;
//		return false;
	}
	
	/**
	 * 处理事件
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupOne+处理+onTouchEvent");
		return super.onTouchEvent(event);
//		return true;
	}

}
自定义View,第二个容器View----属于ViewGroup:

public class ViewGroupTwo extends LinearLayout {

	public ViewGroupTwo(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public ViewGroupTwo(Context context) {
		super(context);
	}

	
	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		//getParent().requestDisallowInterceptTouchEvent(true);//本质上不让父组件的dispatchtouchevent生效
		ActionUtiles.processEvent(event, "ViewGroupTwo+分发+dispatchTouchEvent");
		return super.dispatchTouchEvent(event);
//		return false;
//		return true;
	}
	
	@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupTwo+拦截+onInterceptTouchEvent");
		return super.onInterceptTouchEvent(event);
//		return true;
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupTwo+处理+onTouchEvent");
		return super.onTouchEvent(event);
	}

}
加入一个没有子组件的View,类型类似TextView:

public class MyTextView extends TextView {

	public MyTextView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public MyTextView(Context context) {
		super(context);
	}
	
	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "MyTextView+分发+dispatchTouchEvent");
		return super.dispatchTouchEvent(event);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "MyTextView+处理+onTouchEvent");
		  return super.onTouchEvent(event);//回传机制
		//return true;//事件消费,不回传
		//return false;//和默认效果一样,回传机制
	}
}


因为每个类,每个事件方法都需要写如下代码:

switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(name, "按下ACTION_DOWN");
            break;
        case MotionEvent.ACTION_CANCEL:
            Log.d(name, "取消ACTION_CANCEL");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.e(name, "移动ACTION_MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Log.w(name, "松开ACTION_UP");
            break;
        case MotionEvent.ACTION_OUTSIDE:
            Log.v(name, "点击外面ACTION_OUTSIDE");
            break;

        default:
            break;
        }

功能大家都一样,因此抽取一个Utis类来处理这个方法即可,如下:

public class ActionUtiles {
	/**
	 * 
	 * @param event处理的事件
	 * @param name组件的名称哪个类触发打印的)
	 */
	public static void processEvent(MotionEvent event,String name){
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			Log.i(name, "按下ACTION_DOWN");
			break;
		case MotionEvent.ACTION_CANCEL:
			Log.d(name, "取消ACTION_CANCEL");
			break;
		case MotionEvent.ACTION_MOVE:
			Log.e(name, "移动ACTION_MOVE");
			break;
		case MotionEvent.ACTION_UP:
			Log.w(name, "松开ACTION_UP");
			break;
		case MotionEvent.ACTION_OUTSIDE:
			Log.v(name, "点击外面ACTION_OUTSIDE");
			break;

		default:
			break;
		}
	}
}

为了测试不同事件的打印情况,log使用不同的日志级别进行区分。

接着写一个复合上边图层的布局:

<com.example.event.ViewGroupOne 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff0000"
    android:orientation="vertical" >

    <com.example.event.ViewGroupTwo
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="#00ff00" >

        <com.example.event.MyTextView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#0000ff" >
        </com.example.event.MyTextView>
    </com.example.event.ViewGroupTwo>

</com.example.event.ViewGroupOne>
注意:记得自定义View需要写类的全称。

好了,废话不多说,直接运行程序说话。通多点击手机屏幕。对所有的可能情况全部罗列如下:

一、默认情况,对生成的事件方法返回值什么也不做。返回值都是默认情况:



事件1:点击红色位置。看log输出

按下:可以看到,整个事件是Activity先得到事件,调用它的dispatchTouchEvent分发事件,Activity返回值默认传递给ViewGroupOne,调用ViewGroupOne的dispatchTouchEvent方法,此时这个返回值默认return super.dispatchTouchEvent(event);默认的时候它会把事件传递给自己的onInterceptTouchEvent(MotionEvent event)拦截事件看自己是否需要拦截这个事件,但是由于它的返回值也是默return super.onInterceptTouchEvent(event);认,即没做拦截;事件继续传递,传递给自己的onTouchEvent(MotionEvent event)看自己是否处理这个事件;  自己的onTouchEvent(MotionEvent event)处理方法被调用,但是返回值也是默认,return super.onTouchEvent(event);自己并不处理事件。这样事件到了最里层的View也没有被消费,事件开始回传,回传给父容器Activity的onTouchEvent(MotionEvent event) ,Activity也是默认返回值,表示不做任何处理。整个过程事件都没有做处理和拦截,事件作废。松开后:看一下松开事件:由Activity的dispatchTouchEvent开始分发事件自己返回值是默认情况,把事件直接分发给自己的onTouchEvent(MotionEvent event)方法处理事件,自己返回值是默认情况,事件作废。 整个事件过程,并没有任何地方要处理事件, 由up事件log日志,也可以看到:最后松手时的事件,已经与ViewGroupOne没有任何关系。

事件2:点击绿色区域l。看log输出:


这里就会显而易见了。多了ViewGroupTwo,就多往下传递一层,最里面这层没做处理,最后还是回传回来。一直回传到activity事件全部消失

事件3:点击蓝色区域:

这个肯定在意料之中,不用解释也很清楚为何打印此log了。

二、增加处理:

处理1:蓝色区域,MyTextView修改如下代码:

@Override
	public boolean onTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "MyTextView+处理+onTouchEvent");

		return true;//事件消费,不回传
	}
此时要重新运行程序了再点击蓝色区域,看log:

MyTextView的onTouchEvent(MotionEvent event)返回true的意思是,事件由我来处理。它处理了事件,就不可能出现回传了,出现这种log也是顺理成章了。再看UP事件与分发拦截是一致的。最后在MyTextView中消失。这个时候可以对比一了,一都是默认,可以称之为狭义的回传机制,而二的处理1:我们可以称之为狭义的拦截机制,每一次的往下分发,又可称为狭义的传递机制(称之为狭义,可能逼格略高些,是因为不能代表全部,却也不失一般性;最起码拦截与回传分发大致是什么很清楚了)。那接着就细致开来,从狭义走到广义。

处理2:蓝色区域,MyTextView修改如下代码:

@Override
	public boolean onTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "MyTextView+处理+onTouchEvent");

		return false;//和默认效果一样,回传机制
	}
再运行,打印看log:

和默认效果是一样的。回看默认解析。

处理3:蓝色区域,MyTextView的dispatchTouchEvent方法修改如下代码:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        ActionUtiles.processEvent(event, "MyTextView+分发+dispatchTouchEvent");
       
        return true;
        
    }

看log:


MyTextView的dispatchTouchEvent(MotionEvent event)返回值为true,表示不再分发事件也可以说是拦截事件。此时MyTextView的onTouchEvent方法不会被调用,既然是拦截了事件也不会回传。

处理4:蓝色区域,MyTextView的dispatchTouchEvent方法修改如下代码:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        ActionUtiles.processEvent(event, "MyTextView+分发+dispatchTouchEvent");
        return false;
    }
看log输出:

MyTextView的dispatchTouchEvent(MotionEvent event)返回值为false,表示我自己MyTextView不做事件的分发,那么,我自己的后续方法(onTouchEvent方法)不再被调用执行。直接回传给父控件的onTouchEvent处理方法处理事件,父控件都是默认情况。事件一直回传,直到Activity,事件消失。

说明:上边都是针对蓝色区域点击;这是因为,只有点击了蓝色位置,事件才有可能传递到MyTextView,点击其他位置根本不会调MyTextView的任何方法。

第一次总结:

TextView(没有子组件类型)

dispatchTouchEvent(MotionEvent event)

            return true; //不再分发事件,表示拦截事件,拦截掉不会回传,事件在此消失
            return false;//表示不做分发事件,事件不被拦截,回传给父控件的处理方法

            return super.onTouchEvent() ;//事件传递机制,传递给自己的onTouchEvent方法。

    onTouchEvent()
            return true; //自己处理消费掉事件,事件消费不再回传
            return false;或者 return super.onTouchEvent() ;//效果一样,事件回传给父组件



处理5:点击绿色区域,ViewGroupTwo的dispatchTouchEvent方法做如下修改:

@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupTwo+分发+dispatchTouchEvent");	
		return true;
	}
看log:


由于ViewGroupTwo的dispatchTouchEvent方法返回值为true,则事件被拦截,不会往下传递同时也不会回传。事件在此消失。

处理6:点击绿色区域,ViewGroupTwo的dispatchTouchEvent方法做如下修改:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        ActionUtiles.processEvent(event, "ViewGroupTwo+分发+dispatchTouchEvent");
        return false;
    }
看log:


由于ViewGroupTwo的dispatchTouchEvent方法返回值为false,这跟MyTextView的dispatchTouchEvent返回值为false时候原因是一样的。这个时候,表示我自己不做事件分发,自己的onInterceptTouchEvent方法不会被调用(由于MyTextView没有onInterceptTouchEvent,它是onTouchEvent方法不会被调用),做回传机制。回传给父控件的处理方法,调用父控件的 onTouchEvent方法。父控件默认值,一直传递到Activity,最终事件消失。

说明:这里点击绿色区域和蓝色区域效果是一样的,确切的说没必要点击蓝色区域测试。因为,dispatchTouchEvent并不会直接影响子控件的调用情况。他只决定自己的onInterceptTouchEvent方法是否会被调用。

处理6:点击蓝色区域,ViewGroupTwo的onInterceptTouchEvent方法做如下修改:

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        ActionUtiles.processEvent(event, "ViewGroupTwo+拦截+onInterceptTouchEvent");
        return true;
    }
看log:


由于ViewGroupTwo的onInterceptTouchEvent方法返回值为true,表示我要拦截事件,而且事件交给我自己的处理事件,看处理不处理,如果不处理,则回传机制,回传事件给父控件的处理方法,看是否处理事件。

说明:这里点击绿色区域和蓝色区域效果是一样的,虽然ViewGroupTwo的onInterceptTouchEvent方法可能与孩子控件的dispatchTouchEvent方法有影响,但是由于自己的onInterceptTouchEvent的返回值为true,表示拦截事件,那么直接调用自己的onTouchEvent方法,而不会去调用孩子的dispatchTouchEvent方法。因而,点击蓝色绿色位置其实效果是一样的。

处理7:点击蓝色区域,ViewGroupTwo的onInterceptTouchEvent方法做如下修改:

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        ActionUtiles.processEvent(event, "ViewGroupTwo+拦截+onInterceptTouchEvent");
        
        return false;
    }


可以看到,此时跟默认return super.onInterceptTouchEvent(event);效果是一摸一样的。

说明:这个时候点击绿色区域就不行了。因为点击绿色区域,看不到事件传递给孩子组件的过程。

处理8:点击蓝色区域,ViewGroupTwo的onTouchEvent方法做如下修改:

注意:这个方法想要执行,前提是在ViewGroupTwo的onInterceptTouchEvent方法返回true的时候,或者孩子组件都不做事件处理的时候。因为,自己的onInterceptTouchEvent为true拦截后调用自己的onTouchEvent方法是否做处理。孩子组件如果都是默认,为回传机制,最终肯定会回到自己的onTouchEvent处理事件。

@Override
	public boolean onTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupTwo+处理+onTouchEvent");
	
		return true;

	}

看log:


ViewGroupTwo的onTouchEvent方法返回了true,表示调用了我之后,我就做处理事件。由于ViewGroupTwo的onInterceptTouchEvent返回值为默认或者false,不会立即调用自己的onTouchEvent处理事件, 我们按下的时候,事件还是继续分发给孩子组件,孩子组件没处理事件,就会回传给自己的onTouchEvent,问自己是否处理?true,处理事件,事件在此消失。同样地,这里点击绿色区域也没法测试,原因同上。我们可以看一下UP事件,UP事件直观的告诉我们,事件是由自己处理的。

测试ViewGroupTwo的onTouchEvent方法返回了false的时候,跟默认的return super.onTouchEvent(event);效果是一样的。这个可以自行测试。

处理9:混合处理点击蓝色区域,ViewGroupTwo的onTouchEvent方法和onInterceptTouchEvent方法做如下修改:

@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupTwo+拦截+onInterceptTouchEvent");
		return true;
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupTwo+处理+onTouchEvent");
		return true;
	}

看log日志:


相信这是在意料之中的事情了吧?由于我自己的onInterceptTouchEvent方法返回值true,表示我要做拦截事件,那么孩子的任何方法肯定不会被调用了,不会得到事件。直接调用自己的处理方法onTouchEvent。问自己处理不处理?返回值为true,处理事件了,则事件终止,被自己消费掉。这个时候,点击绿色区域效果也是一样的,这是因为自己的onInterceptTouchEvent方法返回值true,孩子压根得不到点击事件。

第二次总结:

2,ViewGroup
      dispatchTouchEvent
            return true;//事件分发拦截、事件回传拦截。自己消费,不往下(子组件)(不分发)、不往上传递(不回传)
            return false;//事件拦截、事件回传。自己不处理,回传给父组件onTouchEvent方法处理
            return super.dis.....;//事件分发。问自己onInterceptTouchEvent是否拦截
                   问自己  >onInterceptTouchEvent
                                true:  肯定会调用自己onTouchEvent();看是否自己消费事件。但是可能会去孩子那里执行一下MotionEvent.ACTION_CANCEL:,给孩子打声招呼。
                                false 或  super: 两者效果相同,事件传递,默认往下传递,调用孩子组件的dispatchTouchEvent
                              
                                   onInterceptTouchEvent返回true时:问自己>onTouchEvent()
                                              true: 自己消费(不回传)
                                              false 或 super: 两者效果相同,继续回传,回传给父亲组件的onTouchEvent()


处理10:ViewGroupOne的dispatchTouchEvent方法做如下修改,点击任意区域:

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupOne+分发+dispatchTouchEvent");
		return true;
	}
运行程序,点击蓝色位置看log:

其实你会发现。不管点击屏幕哪个位置,都打印这样的log

这是因为,ViewGroupOne的dispatchTouchEvent返回true了,相当于在ViewGroupOne的分发事件地方就拦截了事件(称之为拦截,也因为没有回传,从UP日志里也可以看出没有回传)。事件消失。

处理11:ViewGroupOne的dispatchTouchEvent方法做如下修改,点击任意位置:

@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupOne+分发+dispatchTouchEvent");

		return false;
	}

返回false,表示自己的dispatchTouchEvent不做处理,即不做分发事件。做回传,回传给activity的onTouchEvent处理,最后事件消失。这里同样点击任意位置都是这个log,因为dispatchTouchEvent只有返回默认的时候,才会分发事件。

注意:ViewGroupOne的dispatchTouchEvent方法默认返回super.dispatchTouchEvent(event);的时候是要传递给自己的onInterceptTouchEvent方法的,问这个方式是否做拦截。上边其实已经不知不觉介绍了这个情况。此时可以总结,只有在ViewGroupOne的dispatchTouchEvent方法默认返回super.dispatchTouchEvent(event);的时候才会去调用自己的onInterceptTouchEvent(MotionEvent event) 拦截事件。其他都属于拦截事件,返回true的话甚至连回传机制都消失。

处理12:ViewGroupOne的onInterceptTouchEvent方法做如下修改,点击红色区域:

@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupOne+拦截+onInterceptTouchEvent");
		return true;
	}
log情况

自己的onInterceptTouchEvent(MotionEvent event)拦截事件返回为true,表名自己要拦截,不向下传递。调用自己的onTouchEvent(MotionEvent event)问自己是否处理。由于自己默认不处理,又回传到activity事件消失。同样的,这里点击任何位置log日志都是如此。因为,我拦截事件,只允许事件调用自己的onTouchEvent方法问是否自己做处理,如果自己不作处理事件回传了,如果自己处理事件,事件就会消失。

处理13:ViewGroupOne的onInterceptTouchEvent(MotionEvent event)拦截事件返回false

@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupOne+拦截+onInterceptTouchEvent");
		return false;
	}

又是默认的机制。

处理14:ViewGroupOne的onTouchEvent做如下处理:

注意:这个方法想要执行,前提是在ViewGroupOne的onInterceptTouchEvent方法返回true的时候,或者孩子组件都不做事件处理的时候。因为,自己的onInterceptTouchEvent为true拦截后调用自己的onTouchEvent方法是否做处理。孩子组件如果都是默认,为回传机制,最终肯定会回到自己的onTouchEvent处理事件。

@Override
	public boolean onTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupOne+处理+onTouchEvent");
		return true;
	}

看log:


返回true。当孩子组件都做默认处理,事件会回传到自己的onTouchEvent方法。  自己处理事件,也就没了回传机制。事件在此消失。

处理15:ViewGroupOne的onTouchEvent返回为false。不用打印也可以清楚,与默认效果是一样的。


处理16:混合处理。ViewGroupOne的onTouchEvent和onInterceptTouchEvent做如下处理,点击蓝色区域:

	@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupOne+拦截+onInterceptTouchEvent");
		return true;
	}
	

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		ActionUtiles.processEvent(event, "ViewGroupOne+处理+onTouchEvent");
		return true;
 	}
看log:

由于自己的onInterceptTouchEvent方法返回为true,表示我要处理事件,不再往下传递事件,调用自己的onTouchEvent方法,问自己是否处理,返回了true,说明自己处理事件。事件在此消费,事件消失。


对于ViewGroupTwo和ViewGroupOne总结部分是一摸一样的。就不再多赘述。最后,再用一张草图做一个收尾::


此草图虽然潦草,但是最能说明问题,基本包含了所有可能的情况。


对于拦截机制详细介绍就完毕了,但是除了上边这些情况外,还有许多的分支情况;但是大同小异,仔细分析一下,就能得出正确的结论。以后此专栏可能还会再次探讨类似问题,下一次讨论应该是直接通过源码来分析这个问题。


喜欢的话可以关注我博客,有问题大家一起交流。也可以动手微信扫描下方二维码查看更多安卓文章:


打开微信搜索公众号  Android程序员开发指南  或者手机扫描下方二维码 在公众号阅读更多Android文章。

微信公众号图片:



原文地址:https://www.cnblogs.com/wanghang/p/6299584.html