在一个Activity界面中点击屏幕,将产生一系列事件,如果点击在一个view上则Activity事件和view的touch相关流程都会执行,如果该view还包含在一个ViewGruop中,则ViewGuop中的相关流程也会执行,此时需要弄清楚所有相关流程的执行顺序是什么,首先看下这三者的相关方法
Activity:
//Activity中的事件分发方法
public boolean dispatchTouchEvent(MotionEvent ev)
//Activity的touch事件监听方法
public boolean onTouchEvent(MotionEvent event)
ViewGroup:
//ViewGuop中的事件分发方法
public boolean dispatchTouchEvent(MotionEvent ev)
//ViewGuop的打断分发流程的方法
public boolean onInterceptTouchEvent(MotionEvent ev)
//ViewGuop的OnTouchListener监听
viewGroup.setOnTouchListener(new OnTouchListener(){...})
public boolean onTouchEvent(MotionEvent event)
View:
//View中的事件分发方法
public boolean dispatchTouchEvent(MotionEvent event)
//View的OnTouchListener监听
view.setOnTouchListener(new OnTouchListener(){...})
public boolean onTouchEvent(MotionEvent event)
如上述情况,点击view,则其执行流程如下示:
由图可见,touch一开始,最先由Activity负责分发事件,然后到ViewGroup然后到里面的View,而事件的消费过程顺序相反
该结论通过简单的demo即可验证,写一个ViewGroup包含View的界面,此处使用LinearLayout作为ViewGuoup,用LinearLayout里包含的TextView作为View,布局代码:
<com.view.event.TestLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_1"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}" >
<com.view.event.TestTextView
android:id="@+id/text_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:padding="5dp"
android:background="#55ffcc99"
android:text="西伯利亚虎(学名:Panthera tigris ssp.altaica):又称东北虎,是老虎的最大亚种。是现代体型最大的肉食性猫科动物,野生西伯利亚虎体色夏毛棕黄色,冬毛淡黄色。背部和体侧具有多条横列黑色窄条纹,通常2条靠近呈柳叶状。头大而圆,前额上的数条黑色横纹,中间常被串通,极似“王”字,故有“丛林之王”的美称。" />
</com.view.event.TestLinearLayout>
LinearLayout和TextView都重写了,只是为了方便看打印信息
public class TestLinearLayout extends LinearLayout{
public TestLinearLayout(Context context ) {
super(context );
// TODO Auto-generated constructor stub
}
public TestLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogShow.i("TestLinearLayout dispatchTouchEvent() execute");
return super.dispatchTouchEvent(ev);
// return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogShow.i("TestLinearLayout onInterceptTouchEvent() execute");
return super.onInterceptTouchEvent(ev);
// return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogShow.i("TestLinearLayout onTouchEvent() execute");
return super.onTouchEvent(event);
// return true;
}
}
public class TestTextView extends TextView{
public TestTextView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public TestTextView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogShow.i("TestTextView dispatchTouchEvent() execute");
return super.dispatchTouchEvent(event);
// return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
LogShow.i("TestTextView onTouchEvent() execute");
// TODO Auto-generated method stub
return super.onTouchEvent(event);
// return false;
}
}
Activity中设置了TestLinearLayout和TestTextView的OnTouchListener方法,并且重写了Activity的dispatchTouchEvent()方法和onTouchEvent()
public class MainActivity extends Activity {
TestTextView mTextView;
TestLinearLayout mLinearLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews(){
mLinearLayout = (TestLinearLayout)findViewById(R.id.layout_1);
mLinearLayout.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
LogShow.i("mLinearLayout execute");
return false;
}
});
mTextView = (TestTextView)findViewById(R.id.text_1);
mTextView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
LogShow.i("mTextView execute");
return false;
}
});
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
LogShow.i("mActivity execute");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
LogShow.i("mActivity execute");
return super.onTouchEvent(event);
}
点击mTextView时查看打印信息:
其打印顺序即为上图中的执行流程。由打印可见,在Activity的onToucheEvent()方法第一次执行后,开始反复执行Activity的dispatchTouchEvent()方法和onTouchEvent()方法,不断执行touch相关方法是因为MotionEvent 是一个复合事件,执行多个操作,常用的为:
MotionEvent.ACTION_DOWN:手指按下
MotionEvent.ACTION_MOVE:手指移动
MotionEvent.ACTION_UP:手指抬起
手指接触屏幕时,其实是执行了不同的事件,通过打印查看,其执行是 1次Down + n次Move + 1次Up ,多次Move是因为屏幕捕捉到手指极小的抖动产生Move事件
而其全部方法执行一次后就只执行Activity的方法,这是因为该事件在整个过程中没有被消耗,则View层面不再接受该事件。
接下来的问题是在实际使用中,哪些方法需要使用,哪些要屏蔽,哪些方法会影响流程的执行,可以按照上述顺序逐个方法测试,以便弄清楚他们之间的关系。
(1)从最先的分发开始,Activity的dispatchTouchEvent()源码:
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
其中的getWindow().superDispatchTouchEvent(ev)即把事件交给View处理,从注释中看 You can override this to intercept all touch screen events before they are dispatched to the window.你可以重写该方法,打断从窗口分发的所有屏幕事件。也就是说,该方法被重写了,则后续过程都不再执行。
把Activity中的方法修改一下
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
LogShow.i("Activity dispatchTouchEvent() execute");
// return super.dispatchTouchEvent(ev);
return true; // or return false;
}
然后单击mTextView打印信息为:
可以看到,只有dispatchTouchEvent()本身的打印出现了,说明其他过程全部被打断。
(2)ViewGuoup中的dispatchTouchEvent()方法
让其返回false,方法改为:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogShow.i("TestLinearLayout dispatchTouchEvent() execute");
// return super.dispatchTouchEvent(ev);
return false;
}
打印结果:
可以看到,打印出了前面的流程,中间的流程被打断,而最后Activity中的onTouchEvent()方也执行了,说明ViewGruop和其内部的View的touch事件屏蔽了,Activity级的相关流程不受影响。
然后把上述方法的返回修改为true,打印为:
其后面的所有流程全部被打断了。
(3)ViewGuoup中的onInterceptTouchEvent()方法,该方法的源码:
/**
* Implement this method to intercept all touch screen motion events. This
* allows you to watch events as they are dispatched to your children, and
* take ownership of the current gesture at any point.
*
* <p>Using this function takes some care, as it has a fairly complicated
* interaction with {@link View#onTouchEvent(MotionEvent)
* View.onTouchEvent(MotionEvent)}, and using it requires implementing
* that method as well as this one in the correct way. Events will be
* received in the following order:
*
* <ol>
* <li> You will receive the down event here.
* <li> The down event will be handled either by a child of this view
* group, or given to your own onTouchEvent() method to handle; this means
* you should implement onTouchEvent() to return true, so you will
* continue to see the rest of the gesture (instead of looking for
* a parent view to handle it). Also, by returning true from
* onTouchEvent(), you will not receive any following
* events in onInterceptTouchEvent() and all touch processing must
* happen in onTouchEvent() like normal.
* <li> For as long as you return false from this function, each following
* event (up to and including the final up) will be delivered first here
* and then to the target's onTouchEvent().
* <li> If you return true from here, you will not receive any
* following events: the target view will receive the same event but
* with the action {@link MotionEvent#ACTION_CANCEL}, and all further
* events will be delivered to your onTouchEvent() method and no longer
* appear here.
* </ol>
*
* @param ev The motion event being dispatched down the hierarchy.
* @return Return true to steal motion events from the children and have
* them dispatched to this ViewGroup through onTouchEvent().
* The current target will receive an ACTION_CANCEL event, and no further
* messages will be delivered here.
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
一大堆的注释,内容是直接返回了一个false,看@return的解释:返回true获取子view的手势事件,并通过
onTouchEvent()派发给ViewGruop(),也就是说,如果重写该方法返回false,流程不受影响,如果返回true,子view的touch事件不会执行,而是执行GruopView的onTouchEvent()。
首先返回false试一下,结果是返回所有touch方法,此时确实不影响流程
然后返回true
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogShow.i("execute");
// return super.onInterceptTouchEvent(ev);
return true;
}
打印结果是:
如上所述,此时屏蔽了子view的touch流程
(4)View中的dispatchTouchEvent()方法,源码:
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
从注释中看到,如果当前的View消耗了touch事件,则返回true,否则返回false。
当返回false时,改事件没有被消耗,执行View的touch方法,修改的代码
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogShow.i("TestTextView dispatchTouchEvent() execute");
// return super.dispatchTouchEvent(event);
return false;
}
得到的打印:
可以看到,由于事件没有被消耗,被传递到上一层mLinearlayout的touch方法,此时该方法返回的也是false,继续传递到Activity的onTouch()方法。由于该事件在ViewGruop和View层面没有被消耗,不再执行ViewGruop和View层面的touh流程,接下来往复执行Activity级别的touch流程。
当该方法返回true时,打印信息:
可以看到,touch流程执行到View的dispatchTouchEvent()方法后,就重新反复执行,这是因为此处返回true,相当于消耗了touch事件,则后续ViewGroup和Activity中的流程不再执行,同时由于事件被消耗,touch流程又从最开始执行,事件到了View的dispatchTouchEvent()被消耗,往复执行。
(5)View的onTouch()方法,默认返回false,打印为:
其实就是本篇第一个打印信息,按顺序执行到Activity的onTouchEvent()方法后,事件没有被消耗掉,不再进入view的touch流程,在Activity层面往复执行。
把返回值改为true:
mTextView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
LogShow.i("TestTextView onTouch() execute");
return true;
}
});
打印结果是:
跟上述的执行流程是类似的,执行到View的onTouch()方法,事件被消耗掉,后续流程不再执行,然后重新从Activity的分发开始,往复执行。
(6)View的onTouchEvent()方法,返回false时,流程不收影响
返回true:
跟上述流程类似,执行到View的onTouchEvent()方法事件被消耗,后续流程不再执行,然后重新从Activity的分发开始,往复执行。
(7)ViewGuoup的onTouch()方法
在返回false的情况下流程不受影响。
在返回true的情况下,打印为:
(8)ViewGuoup的onTouchEvent()方法
在返回false的情况下流程不受影响。
在返回true的情况下,打印为:
(9)onTouchEvent返回false或者true,打都与第一个打印一致。
View的onTouch()和onClick都重写了情况下:
在MainActivity中的增加
mTextView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
LogShow.i("TestTextView onClick() execute");
}
});
得到的打印结果是:
可以看到onClick方法在最后一次执行,onTouchEvent()后面打印出来,可以判断onClick方法是在onTouchEvent()的手指抬起操作中完成,查看View源码即可证实这一点。
s://github.com/benweet/stackedit