Android中的touch事件

Android中的事件

Touch事件,四种状态:

ACTION_DOWN     ——>   表示按下了屏幕,一个事件必然从ACTION_DOWN开始
ACTION_MOVE      ——>   表示移动手势
ACTION_UP            ——>  表示离开屏幕
ACTION_CANCEL  ——>   表示取消手势,一般由程序产生,不会由用户产生

一个ACTION_DOWN, n个ACTION_MOVE,1个ACTION_UP,就构成了Android中众多的事件。

Android中的事件onClick, onScroll, onFling等等,都是由许多个Touch组成的。

一个原则,所有的touch事件都是从父容器开始向下传递的,呈U字形。


View事件处理机制核心代码

Android中诸如ImageView、textView、Button等控件都没有重写View的dispatchTouchEvent方法,所以View的事件处理机制对这些控件都有效。


View.java(基于android2.3.3):

[html]  view plain copy
  1. public boolean dispatchTouchEvent(MotionEvent event) {//返回true,表示该View内部消化掉了所有事件。返回false,表示View内部只处理了ACTION_DOWN事件,事件继续传递,向上级View(ViewGroup)传递。  
  2.   
  3.         if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
  4.                 && li.mOnTouchListener.onTouch(this, event)) {//此处的onTouch方式就是回调的我们注册OnTouchListener时重写的onTouch()方法  
  5.             return true;  
  6.         }  
  7.    
  8.         if (onTouchEvent(event)) {// onTouchEvent参考下面源码  
  9.             return true;  
  10.         }  
  11.     ...  
  12. }  


[java]  view plain copy
  1. public boolean onTouchEvent(MotionEvent event) {  
  2.     ...  
  3.   
  4.     // 当前onTouch的组件必须是可点击的比如Button,ImageButton等等,此处CLICKABLE为true,才会进入if方法,最后返回true。  
  5. 如果是ImageView、TexitView这些默认为不可点击的View,此处CLICKABLE为false,最后返回false。当然会有特殊情况,如果给这些View设置了onClick监听器,此处CLICKABLE也将为true,参考下面源码  
  6.     if (((viewFlags & CLICKABLE) == CLICKABLE ||     
  7.             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
  8.         switch (event.getAction()) {  
  9.             case MotionEvent.ACTION_UP:  
  10.                 ...  
  11.                             if (!post(mPerformClick)) {  
  12.                                 performClick();// 实际就是回调了我们注册的OnClickListener中重新的onClick()方法,源码下面源码  
  13.                             }  
  14.                  ...  
  15.                 break;  
  16.   
  17.             case MotionEvent.ACTION_DOWN:  
  18.                ...  
  19.                 break;  
  20.   
  21.             case MotionEvent.ACTION_CANCEL:  
  22.                 ...  
  23.                 break;  
  24.   
  25.             case MotionEvent.ACTION_MOVE:  
  26.                ...  
  27.                 break;  
  28.         }  
  29.         return true;  
  30.     }  
  31.   
  32.     return false;  
  33. }  


[java]  view plain copy
  1. public void setOnClickListener(OnClickListener l) {  
  2.     if (!isClickable()) {  
  3.         setClickable(true);  
  4.     }  
  5.     getListenerInfo().mOnClickListener = l;  
  6. }  


[java]  view plain copy
  1. public boolean performClick() {  
  2.     ...  
  3.   
  4.     if (li != null && li.mOnClickListener != null) {  
  5.         ...  
  6.         li.mOnClickListener.onClick(this);  
  7.         return true;  
  8.     }  
  9.   
  10.     return false;  
  11. }  


总结:

只有我们注册OnTouchListener时重写的onTouch()方法中返回false  ——> 执行onTouchEvent方法 ——>  导致onClick()回调方法执行

onTouch()方法返回true ——> onTouchEvent方法不执行 ——>  导致onClick()回调方法不会执行


ViewGroup事件处理机制核心代码

Android中诸如LinearLayout等的五大布局控件,都是继承自ViewGroup,而ViewGroup本身是继承自View,所以ViewGroup的事件处理机制对这些控件都有效。


ViewGroup.java(基于android2.3.3):

[java]  view plain copy
  1. @Override  
  2. public boolean dispatchTouchEvent(MotionEvent ev) {  
  3.   
  4.     ...  
  5.       
  6.     if (action == MotionEvent.ACTION_DOWN) {  
  7.         if (mMotionTarget != null) {  
  8.             mMotionTarget = null;  
  9.         }  
  10.         //onInterceptTouchEvent返回false,说明向下传递  
  11.         //onInterceptTouchEvent返回true,说明拦截  
  12.         if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  13.           
  14.             ...  
  15.             // 伪代码如下:  
  16.             //1,找到当前控件子控件  
  17.             //2,判断当前touch的点的坐标(x,y)在哪个子控件的矩形区域内  
  18.             //3,判断当前子控件是viewgroup的子类对象,还是view的子类对象  
  19.                 //3.1  如果是viewgroup的子类: 调用其dispatchTouchEvent方法,上述操作再来一遍  
  20.                 //3.2  view 尝试让当前view去处理这个事件(  
  21.                              true,dispatchTouchEvent方法结束,并且返回true  
  22.                              false,dispatchTouchEvent继续向下执行)  
  23.                   
  24.             ...  
  25.               
  26.         }  
  27.     }  
  28.       
  29.     ...  
  30.       
  31.      target = mMotionTarget  
  32.     //target一定是null  
  33.     if (target == null) {  
  34.       
  35.         ...  
  36.           
  37.         //调用当前viewgroup的父View的处理事件的方法  
  38.         return super.dispatchTouchEvent(ev);  
  39.     }  
  40.       
  41.    ...  
  42.      
  43. }  


[java]  view plain copy
  1. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  2.     return false;// 默认返回false  
  3. }  


总结:

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时,内部处理所有的事件,换句话说,后续事件将继续传递给该view的onTouchEvent()处理
返回false时,事件会向上传递,由onToucEvent来接受,如果最上面View中的onTouchEvent也返回false的话,那么事件就会消失


综合案例分析

以下摘自:http://www.longdw.com/android-onintercepttouchevent-ontouchevent/

源码:

[java]  view plain copy
  1. public class MainActivity extends Activity {  
  2.     Group1 group1;  
  3.     Group2 group2;  
  4.     MyTextView myTv;  
  5.   
  6.     /** Called when the activity is first created. */  
  7.     @Override  
  8.     public void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.           
  11.           
  12.         //--group1  
  13.         //----|  
  14.         //-------group2  
  15.         //---------|  
  16.         //------------myTv  
  17.           
  18.         group1 = new Group1(this);  
  19.         group2 = new Group2(this);  
  20.         myTv = new MyTextView(this);  
  21.         group2.addView(myTv, new LayoutParams(LayoutParams.FILL_PARENT,  
  22.                 LayoutParams.FILL_PARENT));  
  23.         group1.addView(group2, new LayoutParams(LayoutParams.FILL_PARENT,  
  24.                 LayoutParams.FILL_PARENT));  
  25.         setContentView(group1);  
  26.     }  
  27. }  


[java]  view plain copy
  1. public class Group1 extends FrameLayout {  
  2.   
  3.     public Group1(Context context) {  
  4.         super(context);  
  5.         // TODO Auto-generated constructor stub  
  6.     }  
  7.   
  8.     @Override  
  9.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  10.         // TODO Auto-generated method stub  
  11.         Log.d(Constant.LOGCAT, "Group1 onInterceptTouchEvent触发事件:"+Constant.getActionTAG(ev.getAction()));  
  12.         return false;  
  13.     }  
  14.   
  15.     @Override  
  16.     public boolean onTouchEvent(MotionEvent event) {  
  17.         // TODO Auto-generated method stub  
  18.         Log.d(Constant.LOGCAT, "Group1 onTouchEvent触发事件:"+Constant.getActionTAG(event.getAction()));  
  19.         return false;  
  20.     }  
  21. }  


[java]  view plain copy
  1. public class Group2 extends FrameLayout {  
  2.   
  3.     public Group2(Context context) {  
  4.         super(context);  
  5.         // TODO Auto-generated constructor stub  
  6.     }  
  7.   
  8.     @Override  
  9.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  10.         // TODO Auto-generated method stub  
  11.         Log.d(Constant.LOGCAT, "Group2 onInterceptTouchEvent触发事件:"+Constant.getActionTAG(ev.getAction()));  
  12.         return false;  
  13.     }  
  14.   
  15.     @Override  
  16.     public boolean onTouchEvent(MotionEvent event) {  
  17.         // TODO Auto-generated method stub  
  18.         Log.d(Constant.LOGCAT, "Group2 onTouchEvent触发事件:"+Constant.getActionTAG(event.getAction()));  
  19.         return false;  
  20.     }  
  21. }  


[java]  view plain copy
  1. public class MyTextView extends TextView {  
  2.   
  3.     public MyTextView(Context context) {  
  4.         super(context);  
  5.         this.setGravity(Gravity.CENTER);  
  6.         this.setText("点击我!");  
  7.         // TODO Auto-generated constructor stub  
  8.     }  
  9.   
  10.     @Override  
  11.     public boolean onTouchEvent(MotionEvent event) {  
  12.         // TODO Auto-generated method stub  
  13.         Log.d(Constant.LOGCAT, "MyTextView onTouchEvent触发事件:"+Constant.getActionTAG(event.getAction()));  
  14.         return false;  
  15.     }  
  16. }  


[java]  view plain copy
  1. public class Constant {  
  2.     public static final String LOGCAT = "logcat";  
  3.   
  4.     public static String getActionTAG(int action) {  
  5.         switch (action) {  
  6.         case 0:  
  7.             return "ACTION_DOWN";  
  8.         case 1:  
  9.             return "ACTION_UP";  
  10.         case 2:  
  11.             return "ACTION_MOVE";  
  12.         default:  
  13.             return "NULL";  
  14.         }  
  15.     }  
  16. }  

分别重写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的结果证实了这一猜测:


还有一篇文章也比较好,可作为这个案例的补充,http://orgcent.com/android-touch-event-mechanism/


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值