本文主要针对dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent三个方法,通过简单的例子来简单的介绍下。
1、dispatchTouchEvent作用:决定事件是否由onInterceptTouchEvent来拦截处理。
- 返回super.dispatchTouchEvent时,由onInterceptTouchEvent来决定事件的流向
- 返回false时,不会继续分发事件,自己内部只处理了ACTION_DOWN
- 返回true时,不会继续分发事件,自己内部处理了所有事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP)
2、onInterceptTouchEvent作用:拦截事件,用来决定事件是否传向子View
- 返回true时,拦截后交给自己的onTouchEvent处理
- 返回false时,拦截后交给子View来处理
3、onTouchEvent作用:事件最终到达这个方法
- 返回true时,内部处理所有的事件
- 返回false时,事件会向上传递,由onToucEvent来接受,如果最上面View中的onTouchEvent也返回false的话,那么事件就会消失。
Android官方文档上:
onInterceptTouchEvent()与onTouchEvent()的机制:
- down事件首先会传递到onInterceptTouchEvent()方法
- 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之return false,那么后续的move, up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理
- 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。
- 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理
- 如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
这是摘自网络:
More:
1. 一个事件必然从ACTION_DOWN开始,所以当某一个控件处理了一个ACTION_DOWN事件,可以理解直到下一个ACTION_DOWN之前所有的触摸事件都应当由该控件(target)进行处理。
2. 对于各种listener,显然是由android系统进行处理的。所以Super.onTouchEvent()应当放在子类的该函数的第一行,可以优先保证系统的方法不会被自定义的操作截取而导致没有机会执行。
3. 牢记一个原则,所有的touch事件都是从父容器开始向下传递的,呈U字形
(1) 父容器(类型为 <? Extends ViewGroup>) onInterceptTouchEvent接收到事件,如果return false,转步骤(2),否则转 步骤(3)
(2) 父容器在该位置(ACTION_DOWN发生的位置)存在子控件,如果子控件类型为
<? Extends ViewGroup>,此时该子控件成为父容器,转步骤(1),如果子控件为普通view,即 不是viewgroup的子类,使用子控件转步骤(3),如果不存在子控件,使用父容器转步骤(3)
(3) 该touch事件交给该控件的onTouchEvent进行响应,如果return false,当其存在父容器时,事件传递给父容器,转步骤(3)。当不存在父容器时,该事件被丢弃。如果return true,表示事件被消费了,此touch事件终止。
关于事件传递解释的更好的文章在这里。
下面结合具体的例子来讲解,首先是容器类MyLinearLayout继承自LinearLayout,子View类MyTextView继承自TextView。
MyLinearLayout类:
package ldw . test ;
import android . content . Context ;
import android . util . AttributeSet ;
import android . util . Log ;
import android . view . MotionEvent ;
import android . view . View ;
import android . view . ViewGroup ;
import android . widget . LinearLayout ;
public class MyLinearLayout extends LinearLayout {
private String TAB = "MyLinearLayout" ;
public MyLinearLayout ( Context context , AttributeSet attrs ) {
super ( context , attrs ) ;
}
public MyLinearLayout ( Context context ) {
super ( context ) ;
}
// @Override
// protected void onLayout(boolean changed, int l, int t, int r, int b) {
// int childCount = getChildCount();
// for(int i = 0;i < childCount;i++) {
// View child = getChildAt(i);
// child.layout(l, t, r, b);
// }
// }
@Override
public boolean dispatchTouchEvent ( MotionEvent ev ) {
switch ( ev . getAction ( ) ) {
case MotionEvent . ACTION_DOWN :
Log . d ( TAB , "dispatchTouchEvent action:ACTION_DOWN" ) ;
break ;
case MotionEvent . ACTION_UP :
Log . d ( TAB , "dispatchTouchEvent action:ACTION_UP" ) ;
break ;
}
return super . dispatchTouchEvent ( ev ) ;
}
@Override
public boolean onInterceptTouchEvent ( MotionEvent ev ) {
//true表示拦截后交给自己的onTouchEvent处理,false表示传递给子View
switch ( ev . getAction ( ) ) {
case MotionEvent . ACTION_DOWN :
Log . d ( TAB , "onInterceptTouchEvent action:ACTION_DOWN" ) ;
break ;
case MotionEvent . ACTION_UP :
Log . d ( TAB , "onInterceptTouchEvent action:ACTION_UP" ) ;
break ;
}
return false ;
}
@Override
public boolean onTouchEvent ( MotionEvent event ) {
switch ( event . getAction ( ) ) {
case MotionEvent . ACTION_DOWN :
Log . d ( TAB , "---onTouchEvent action:ACTION_DOWN" ) ;
break ;
case MotionEvent . ACTION_UP :
Log . d ( TAB , "---onTouchEvent action:ACTION_UP" ) ;
break ;
}
return false ;
}
}
|
MyTextView类:
package ldw . test ;
import android . content . Context ;
import android . util . AttributeSet ;
import android . util . Log ;
import android . view . MotionEvent ;
import android . widget . TextView ;
public class MyTextView extends TextView {
private String TAB = "MyTextView" ;
public MyTextView ( Context context , AttributeSet attrs , int defStyle ) {
super ( context , attrs , defStyle ) ;
}
public MyTextView ( Context context , AttributeSet attrs ) {
super ( context , attrs ) ;
}
public MyTextView ( Context context ) {
super ( context ) ;
}
@Override
public boolean dispatchTouchEvent ( MotionEvent event ) {
switch ( event . getAction ( ) ) {
case MotionEvent . ACTION_DOWN :
Log . d ( TAB , "dispatchTouchEvent action:ACTION_DOWN" ) ;
break ;
case MotionEvent . ACTION_UP :
Log . d ( TAB , "dispatchTouchEvent action:ACTION_UP" ) ;
break ;
}
return super . dispatchTouchEvent ( event ) ;
}
@Override
public boolean onTouchEvent ( MotionEvent event ) {
switch ( event . getAction ( ) ) {
case MotionEvent . ACTION_DOWN :
Log . d ( TAB , "---onTouchEvent action:ACTION_DOWN" ) ;
break ;
case MotionEvent . ACTION_UP :
Log . d ( TAB , "---onTouchEvent action:ACTION_UP" ) ;
break ;
}
return false ;
}
}
|
布局文件activity_main.xml
<? xml version = "1.0" encoding = "utf-8" ?>
<ldw.test.MyLinearLayout xmlns : android = "http://schemas.android.com/apk/res/android"
android : id = "@+id/testview"
android : layout_width = "fill_parent"
android : layout_height = "fill_parent"
android : gravity = "center" >
<ldw.test.MyTextView
android : layout_width = "200dip"
android : layout_height = "200dip"
android : text = "@string/hello_world"
android : textSize = "50sp" />
</ldw.test.MyLinearLayout>
|
为了更清晰的理解事件的分发处理机制,最好自己将上面代码运行一下,主要更改下MyLinearLayout中的onInterceptTouchEvent的返回值(true或false),还有两个类中的onTouchEvent的返回值,来观察logcat中的打印结果,基本上和上面总结出来的一致。
记住一点:true就是自己内部消化掉所有事件,false就是继续传递事件。
关于onInterceptTouchEvent和onTouchEvent的详细解释。
public class MainActivity extends Activity {
Group1 group1 ;
Group2 group2 ;
MyTextView myTv ;
/** Called when the activity is first created. */
@Override
public void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState ) ;
//--group1
//----|
//-------group2
//---------|
//------------myTv
group1 = new Group1 ( this ) ;
group2 = new Group2 ( this ) ;
myTv = new MyTextView ( this ) ;
group2 . addView ( myTv , new LayoutParams ( LayoutParams . FILL_PARENT ,
LayoutParams . FILL_PARENT ) ) ;
group1 . addView ( group2 , new LayoutParams ( LayoutParams . FILL_PARENT ,
LayoutParams . FILL_PARENT ) ) ;
setContentView ( group1 ) ;
}
}
|
分别重写Group1和Group2的onInterceptTouchEvent和onTouchEvent方法,重写MyTextView的onTouchEvent方法,最终得到的控件层次结构如下:
1.在默认返回值情况下logcat输出如下:
测试后可知默认情况下和所有方法返回值为false的结果一致,down事件的捕获顺序onInterceptTouchEvent先于onTouchEvent,由于onTouchEvent返回值为false,down事件没被消化,后续的move和up事件没有出现,同时逆序返回到父控件的onTouchEvent方法来捕获,如下图所示:
2.所有onTouchEvent返回值为true情况下logcat输出如下:
输出结果可以看出子控件MyTextView消化了down事件,后续的move和up事件正常捕获,由于down事件被消化,上层的onTouchEvent方法不执行,如下图所示:(三箭头分别指down、move、up事件)
既然如此,如果MyTextView中onTouchEvent方法返回为false,而group1和group2的onTouchEvent方法返回true的结果自然也就如下图的顺序了:
测试输出结果证明了这一猜测顺序,
注意:可能有人对这种情况比较疑惑,ACTION_DOWN还好理解,但是ACTION_MOVE为什么没有经历myTv,而且ACTION_MOVE只经历了group1的onInterceptTouchEvent和group2的onTouchEvent而没有经历group2的onInterceptTouchEvent,我开始也费解,后来想想也是,大家对比第1条,由于onTouchEvent返回了false而没有消耗down事件导致后续的move和up都没有出现,这里也是一样由于myTv中的onTouchEvent返回了false也就是说没有消耗down事件,那么后面的move和up也都不会出现在这个view里面,但是group2截获到了down事件,但后来的move为什么group2中的onInterceptTouchEvent没有执行到呢,原因大家不要忘记了onInterceptTouchEvent的初衷是什么,返回false是让它的子view或viewgroup类处理,而group2的子控件显然是myTv而myTv的onTouchEvent返回了false也就是接收不到后续的move和up事件,也就没必要经过onInterceptTouchEvent来继续分发了(因为分发了也还是接收不到),经过group2的onTouchEvent因为它返回的是true,截获了事件并且消耗了事件。
3.当某个GroupView中的onInterceptTouchEvent方法返回值为true情况下logcat输出如下(如group2):
如果在该方法返回值中返回true,那么子控件将获取不到任何点击事件,转而向自身的onTouchEvent方法转发,如下图所示:
如果onTouchEvent方法返回值都为true,那么根据规律结果就如下图顺序触发:
最后logcat的结果证实了这一猜测,
根据这一顺序规律我们便可复写GroupView中的onInterceptTouchEvent来控制事件的响应者。