Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法

1、 第一个问题是控件本身的onTouch和onClick方法的执行冲突

首先自定义一个MyLayout继承LinearLayout,重写dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent方法,代码如下:

package com.bbdtek.demo;  

import android.content.Context;  
import android.util.AttributeSet;  
import android.util.Log;  
import android.view.MotionEvent;  
import android.widget.LinearLayout;  

public class MyLayout extends LinearLayout {  

@Override  
public boolean dispatchTouchEvent(MotionEvent ev) {  
    Log.e("Demo:","父View的dispatchTouchEvent方法执行了");  
    return super.dispatchTouchEvent(ev);  
}  

@Override  
public boolean onInterceptTouchEvent(MotionEvent ev) {  
    Log.e("Demo:","父View的onInterceptTouchEvent方法执行了");  
    return super.onInterceptTouchEvent(ev);  
}  

@Override  
public boolean onTouchEvent(MotionEvent event) {  
    Log.e("Demo:","父View的onTouchEvent方法执行了");  
    return super.onTouchEvent(event);  
}  

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

public MyLayout(Context context,AttributeSet attr) {  
    super(context,attr);  
}  
}  

在这三个方法中打印一段信息,有助于查看这三个方法的执行流程

下面是自定义一个MyTextView继承TextView,重写dispatchTouchEvent,onTouchEvent方法(这里没有onInterceptTouchEvent方法,原因很简单,就是TextView是一个View没有子View了,不需要拦截信息了)代码如下:

package com.bbdtek.demo;  

import android.content.Context;  
import android.util.AttributeSet;  
import android.util.Log;  
import android.view.KeyEvent;  
import android.view.MotionEvent;  
import android.widget.TextView;  

public class MyTextView extends TextView{  

@Override  
public boolean dispatchTouchEvent(MotionEvent ev) {  
Log.e("Demo:","子View的dispatchTouchEvent方法执行了");  
return super.dispatchTouchEvent(ev);  
}  

@Override  
public boolean onTouchEvent(MotionEvent event) {  
Log.e("Demo:","子View的onTouchEvent方法执行了");  
return super.onTouchEvent(event);  
}  

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

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

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

}  

定义的一个布局文件:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout   
    xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent">  
        <com.bbdtek.demo.MyLayout   
            android:id="@+id/layout"  
            android:layout_width="fill_parent"  
            android:layout_height="fill_parent"  
            android:orientation="vertical">  
            <com.bbdtek.demo.MyButton   
                android:id="@+id/btn"  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"  
                android:text="点我"/>  
            <com.bbdtek.demo.MyTextView  
                android:id="@+id/txt1"   
                android:layout_width="fill_parent"  
                android:layout_height="wrap_content"  
                android:text="我是个好人我是个好人呢我是啊速度将法律上的经费福建省的雷锋精神多了爽肤水两地分居SD卡飓风桑迪快乐分类数据的法律进多少"  
                android:textSize="25dp"/>  
        </com.bbdtek.demo.MyLayout>  
</LinearLayout>  

MyLayout中包含一个MyTextView控件:

package com.bbdtek.demo;  

import android.app.Activity;  
import android.os.Bundle;  
import android.util.Log;  
import android.view.MotionEvent;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.view.View.OnTouchListener;  

public class AndroidDemoActivity extends Activity{  

    public void onCreate(Bundle savedInstanceState) {  
       super.onCreate(savedInstanceState);  
       setContentView(R.layout.main);  
       MyTextView txt1 = (MyTextView)findViewById(R.id.txt1);  
       //MyLayout layout = (MyLayout)findViewById(R.id.layout); 
       txt1.setOnClickListener(new OnClickListener(){  
            @Override  
            public void onClick(View v) {  
            Log.e("Demo:","点击了txt1");  
        }});  
       //给MyTextView定义一个onTouchListener监听器 
       txt1.setOnTouchListener(new OnTouchListener(){  
            @Override  
            public boolean onTouch(View v, MotionEvent event) {  
                if(event.getAction() == MotionEvent.ACTION_DOWN){  
                    Log.e("Demo:","txt1按下");  
                }else if(event.getAction() == MotionEvent.ACTION_UP){  
                    Log.e("Demo:","txt1弹起");  
                }  
                return false;  
            }});  
       //给MyLayout定义一个onTouchListener监听器 
       /*layout.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { Log.e("Demo:","点击了父layout"); }});*/  

    }  
} 

看一下运行结果:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第1张图片

可以看到先执行的是MyTextView的dispatchTouchEvent方法(这个方法是每次触发onTouch方法都会执行的),然后是执行了onTouch方法中的ACTION_UP中的代码,然后执行了MyTextView中的onTouchEvent方法,当用户弹起手指的时候又一次执行了这样的一个过程,最后就是执行了onClick方法,在这里就来看一下onTouchEvent中的源代码:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第2张图片

因为TextView继承View,可以查看View中的onTouchEvent方法,在这个方法中又调用了ViewGroup中的onTouchEvent方法,下面在来看一下ViewGroup中的onTouchEvent方法:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第3张图片

在ACTION_UP中的这段代码就是执行了onClick方法,具体可以看一下performClick方法解释:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第4张图片

mOnClickListener就是OnClickListener监听器,执行了onClick方法,所以上面的onClick方法是在ACTION_UP之后执行了。

下面在来看一下这种情况,现在把onTouch方法的返回值改成true:看一下执行结果:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第5张图片

可以看到首先还是执行了MyTextView的dispatchTouchEvent方法,然后执行了onTouch中的ACTION_DOWN代码,同样当用户的弹起手指的时候执行了同样的过程,现在的问题是这样的执行过程为什么和上面的不一样呢?

首先来看一下有哪些不一样的地方:
第一个不一样的地方就是”子View的onTouchEvent方法执行了“这句话没有执行,那就是MyTextView中的onTouchEvent方法没有执行了,来看一下源码:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第6张图片

在View中的dispatchTouchEvent方法中可以看到,是先执行OnTouchListener监听器中的onTouch方法,如果onTouchListener不为null,并且onTouch方法返回false的时候才执行onTouchEvent方法,现在我们把onTouch方法的返回值变成了true,所以onTouchEvent方法就不执行了。
这里一定要注意onTouchEvent方法和onTouch方法的区别。
第二个不一样的地方就是onClick方法没有执行了,对于这样的问题,在源代码中没有找到答案,但是记住一点就可以了那就是如果onTouch方法返回true,说明View这次消费了这次事件,所以就不会再执行后续的onClick方法了,

从上面的例子可以看出来:onClick方法的执行时依赖于onTouch方法的,下面就总结一下onClick和onTouch两个方法之间的执行关系:

  • 如果该View 是disable 状态:那么给它加上pressed标志位,重绘一下(在屏幕上显示为灰显按下去的效果)
  • 如果该View不是disable状态,并且是clickable,那么在ACTION_DOWN的时候会加上pressed标志位,并启动一个timer(长按事件onLongClickListener)。重绘一下(显示出按下去的效果)。此时还是MOVE事件监视该View的状态,如果这个MOVE滑出了该View的范围,那么会复位View的click状态。UP事件时,会检查是否已经执行过LongPress,如果已经执行了LongPress,那么就不执行了Click,反之,会取消掉LongPress的Timer,然后在执行Click。在UP事件的最后,做一下收尾处理,完事。

所以TouchEvent是Click 和 LongPress的底层实现,View的派生类如Button等等就不需要重写OnTouchEvent。

2、第二个问题是子控件和父控件之间的onTouch执行过程的分析

首先来看一下测试代码:

package com.bbdtek.demo;  

import android.app.Activity;  
import android.os.Bundle;  
import android.util.Log;  
import android.view.MotionEvent;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.view.View.OnTouchListener;  

public class AndroidDemoActivity extends Activity{  

    public void onCreate(Bundle savedInstanceState) {  
       super.onCreate(savedInstanceState);  
       setContentView(R.layout.main);  
       MyTextView txt1 = (MyTextView)findViewById(R.id.txt1);  
       MyLayout layout = (MyLayout)findViewById(R.id.layout);  
       txt1.setOnClickListener(new OnClickListener(){  
            @Override  
            public void onClick(View v) {  
                Log.e("Demo:","点击了txt1");  
            }});  
       //给MyTextView定义一个onTouchListener监听器 
       txt1.setOnTouchListener(new OnTouchListener(){  
            @Override  
            public boolean onTouch(View v, MotionEvent event) {  
                if(event.getAction() == MotionEvent.ACTION_DOWN){  
                    Log.e("Demo:","txt1按下");  
                }else if(event.getAction() == MotionEvent.ACTION_UP){  
                    Log.e("Demo:","txt1弹起");  
                }  
                return false;  
            }});  
     //给MyTextView定义一个onTouchListener监听器 
       layout.setOnTouchListener(new OnTouchListener(){  
            @Override  
            public boolean onTouch(View v, MotionEvent event) {  
                if(event.getAction() == MotionEvent.ACTION_DOWN){  
                    Log.e("Demo:","父layout按下");  
                }else if(event.getAction() == MotionEvent.ACTION_UP){  
                    Log.e("Demo:","父layout弹起");  
                }  
                return false;  
            }});  
       //给MyLayout定义一个onTouchListener监听器 
       layout.setOnClickListener(new OnClickListener(){  
            @Override  
            public void onClick(View v) {  
                Log.e("Demo:","点击了父layout");  
        }});  
    }  
}  

点击MyTextView后观察log信息如下:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第7张图片

这样就可以清楚的看到onTouch的执行过程了:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第8张图片

这三种情况就详细说明了这个执行过程,其中dispatchTouchEvent方法没有标注,比较简单,首先会执行父控件的dispatchTouchEvent方法,下面在来看一种情况,就是在测试代码中将这些代码注释:

txt1.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Log.e("Demo:","点击了txt1");
}});

看一下运行结果:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第9张图片

这里就是把MyTextView的点击监听器注释了,但是运行的结果差距很大:我们一步一步来分析一下:
首先来看一下View的setOnClickListener方法的源代码:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第10张图片

这个方法很简单就是设置监听器mOnClickListener,但是之前还有一段代码那就是setClick(true);从注释中可以看到,如果设置了点击监听器说明这个控件就是可以点击的了,再来看一下setClick方法:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第11张图片

这个方法就是设置一下可以点击的标志变量了,现在再来看一下onTouchEvent方法:

Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法_第12张图片

由于onTouchEvent方法的代码太长了,不要截图,但是大体意思是如果这个控件是可以点击的,那么onTouchEvent方法就返回true,当方法onTouchEvent方法返回true的时候说明这次事件被该控件消费了,不会再往上传递了。
所以,我们给txt1添加onClick监听器的时候,运行结果中可以看到父控件的onTouch方法没有执行,当我们把onClick监听器删除的时候,父控件的onTouch方法执行了,这次的事件被父控件消费了,所以txt1的onTouch中的ACTION_UP中的代码就没有执行了。

为了验证这一点其实很简单,你可以直接设置txt1.setClickable(false);这个方法通过设置false和true来观察结果。
这里还有一个重要的信息是Android中像Button,CheckBox这样的控件默认都是可以点击的,所以不需要setClickable(true)方法来实现了,但是像TextView这样的控件默认是不可点击的,所以要通过setClickable(true)这样的方法来实现。

3、当然你也可以通过设置dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent这三个方法的返回值来观察结果:

Android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件,有必要对它进行深入的了解。
一个最简单的屏幕触摸动作触发了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->ACTION_MOVE->ACTION_MOVE…->ACTION_MOVE->ACTION_UP

当屏幕中包含一个ViewGroup,而这个ViewGroup又包含一个子view,这个时候android系统如何处理Touch事件呢?到底是
ViewGroup来处理Touch事件,还是子view来处理Touch事件呢?我只能很肯定的对你说不一定。呵呵,为什么呢?看看下面我的调查结果你
就明白了。

android系统中的每个View的子类都具有下面三个和TouchEvent处理密切相关的方法:

  1. public booleandispatchTouchEvent(MotionEvent ev) 这个方法用来分发TouchEvent
  2. public boolean onInterceptTouchEvent(MotionEvent ev)这个方法用来拦截TouchEvent
  3. public boolean onTouchEvent(MotionEvent ev)这个方法用来处理TouchEvent

当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View,

TouchEvent最先到达最顶层 view的 dispatchTouchEvent,然后由 dispatchTouchEvent方法进行分发,

如果dispatchTouchEvent返回true,则交给这个view的onTouchEvent处理,

如果 dispatchTouchEvent返回 false,则交给这个 view的 interceptTouchEvent方法来决定是否要拦截这个事件,

如果 interceptTouchEvent返回 true,也就是拦截掉了,则交给它的onTouchEvent来处理,

如果 interceptTouchEvent返回 false,那么就传递给子 view,由子 view 的dispatchTouchEvent再来开始这个事件的分发。

如果事件传递到某一层的子 view的 onTouchEvent上了,这个方法返回了 false,那么这个事件会从这个 view往上传递,都是onTouchEvent来接收。

如果事件传递到某一层的子view的onTouchEvent上了,这个方法返回true,那么这个事件将不会向上传递了,又这个view拦截处理.

而如果传递到最上面的 onTouchEvent也返回 false的话,这个事件就会“消失”,而且接收不到下一次事件

onInterceptTouchEvent()用于处理事件并改变事件的传递方向。处理事件这个不用说了,你在函数内部编写代码处理就可以了。而决定传递方向的是返回值,返回为false时事件会传递给子控件的onInterceptTouchEvent();返回值为true时事件会传递给当前控件的onTouchEvent(),而不在传递给子控件,这就是所谓的Intercept(截断)。

onTouchEvent() 用于处理事件,返回值决定当前控件是否消费(consume)了这个事件。可能你要问是否消费了又区别吗,反正我已经针对事件编写了处理代码?答案是有区别!比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。

转载:
http://blog.csdn.net/jiangwei0910410003/article/details/17504315
http://blog.csdn.net/jiangwei0910410003/article/details/16986039

你可能感兴趣的:(android,onclick,onTouch)