下面分析控件本身的onTouch和onClick方法的执行冲突

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

    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了,不需要拦截信息了)代码如下:

    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);
    }
    }
        

在方法中输出log信息,便于查看方法的执行流程

定义的一个布局文件:

<?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.MyTextView
                android:id="@+id/txt1"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:text="hello world"
                android:textSize="25dp"/>
        </com.bbdtek.demo.MyLayout>
</LinearLayout>
            

MyLayout中包含一个MyTextView控件:

下面来看一下测试代码:

    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");
                    }});*/
        }
    }
     

看一下运行结果:

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

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

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

mOnClickListener就是OnClickListener监听器,执行了onClick方法,所以上面的onClick方法是在ACTION_UP之后执行了。 下面在来看一下这种情况,现在把onTouch方法的返回值改成true:看一下执行结果:

可以看到首先还是执行了MyTextView的dispatchTouchEvent方法,然后执行了onTouch中的ACTION_DOWN代码,同样当用户的弹起手指的时候执行了同样的过程,现在的问题是这样的执行过程为什么和上面的不一样呢? 首先来看一下有哪些不一样的地方: 第一个不一样的地方就是”子View的onTouchEvent方法执行了“这句话没有执行,那就是MyTextView中的onTouchEvent方法没有执行了,来看一下源码:

在View中的dispatchTouchEvent方法中可以看到,是先执行OnTouchListener监听器中的onTouch方法, 如果onTouchListener不为null,并且onTouch方法返回false的时候才执行onTouchEvent方法, 现在我们把onTouch方法的返回值变成了true,所以onTouchEvent方法就不执行了。这里一定要注意onTouchEvent方法和onTouch方法的区别。

第二个不一样的地方就是onClick方法没有执行了,因为onClick是在onTouchEvent执行后执行的,而 onTouchEvent都没有执行,所以onClick更不会执行。 所以如果onTouch方法返回true,说明View这次消费了这次事件,所以就不会再执行后续的onClick方法了。

所以事件传递流程是 onTouch –> onTouchEvent –> onClick。onTouch如果返回true,后面都不会执行。

这里再解释一下为什么ListView中的Item中如果有Button,CheckBox等这样的组件的话,ListView中的setOnItemClick方法就是失效了, 原因是Item没有获取焦点,焦点被Button等控件默认获取到了。ListView无法获得焦点,当然不能响应setOnItemClick。源码如下:

可以看到如果这个控件是可以点击的,那么onTouchEvent方法就返回true,当方法onTouchEvent方法返回true的时候说明这次事件被该控件消费了, 不会再往上传递了。这里还有一个重要的信息是Android中像Button,CheckBox这样的控件默认都是可以点击的,所以不需要setClickable(true)方法来实现了, 但是像TextView这样的控件默认是不可点击的,所以要通过setClickable(true)这样的方法来实现。另外,如果给TextView这样的控件设置监听器mOnClickListener, 就是设置成可点击状态。代码如下:

所以解决ListView中setOnItemClick方法就是失效有两种解决方法:

第一种就是让Button控件失去焦点,可以在布局文件中设置代码:android:focusable=”false”即可。

第二种就是把setOnItemClick方法中的逻辑代码放在adapter里面getView中的contentView的onClick方法中。因为listview无法获得焦点,不能监听, 焦点在contentView里面的控件上,contentView里面的控件可以监听。