关于Android中的View的dispatchTouchEvent

本文探讨了Android中View的dispatchTouchEvent方法在事件传递过程中的作用,解释了为什么onTouch事件优先于onClick执行,以及当onTouch返回true时onClick不执行的原因。通过分析源码,揭示了事件传递和消费的机制,特别是对于ViewGroup中控件的点击和触摸事件的处理流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是个困惑我好久的问题,搞了好久才明白一些,看了很多大牛的技术文章,也写了一些小demo测试了一番,菜鸟在这里和大家分享一下了!
首先,这里涉及到View和ViewGroup,但是View只有事件的响应与否,我的理解是应为View不是容器型的控件,但是ViewGroup是容器型的控件,所以其涉及到事件的传递与回传,所谓回传就是响应!
好了,我们切入主题,

button.setOnClickListener(new OnClickListener() {  
@Override  
public void onClick(View v) {  
    Log.d("TAG", "onClick execute");  
}  
  });  

 button.setOnTouchListener(new OnTouchListener() {  
@Override  
public boolean onTouch(View v, MotionEvent event) {  
    Log.d("TAG", "onTouch execute, action " + event.getAction());  
    return false;  
}  
  });  

当我们触碰到任何一个控件的时候,都会调用该控件的dispatchTouchEvent方法,这里我们查看程序的运行结果如下所示

onTouch execute,action0
onTouch execute,aciton1
onClick execute

可以看到,onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。
但是如果我们把onTouch方法的返回值返回true的话,我们会发现onClick竟然没有执行
追究其原因,我们就要仔细的去看一下View的dispatchTouchEvent源码:

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}  

进入if语句首先我们要知道mOnTouchListener 到底是谁,才能进而判断出其是不是为null,按住ctrl+k我们发现了一个惊天大秘密。

public void setOnTouchListener(OnTouchListener l) {  
    mOnTouchListener = l;  
}  

与那里这里的mOnTouchListener,就是我们写的内部类

new OnTouchListener() {  
@Override  
public boolean onTouch(View v, MotionEvent event) {  
    Log.d("TAG", "onTouch execute, action " + event.getAction());  
    return false;  
}  

我们已经赋值了,它是绝对不会为空的。再看第二个参数(mViewFlags & ENABLED_MASK) == ENABLED,它的意思就是该控件是否可用的enabled,所有控件默认都是可用的,所以这里我们只需要看第三个条件即可,mOnTouchListener.onTouch(this, event),其实这里就是回调啦,这里正是我们自己写的onTouch函数呀,当该函数返回true的时候,disPatcheTouchEvent的返回值就是true,这时代表着onTouch事件是被消耗掉的,所以onclick函数是不会被执行的。
由此可知,onclick事件的执行一定是在onTouchEvent(event)中调用的!所以当onTouch函数返回false的时候,就会调用onTouchEvent函数,好了接下来我们继续看源码

public boolean onTouchEvent(MotionEvent event) {  
    final int viewFlags = mViewFlags;  
    if ((viewFlags & ENABLED_MASK) == DISABLED) {  
        // A disabled view that is clickable still consumes the touch  
        // events, it just doesn't respond to them.  
        return (((viewFlags & CLICKABLE) == CLICKABLE ||  
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
    }  
    if (mTouchDelegate != null) {  
        if (mTouchDelegate.onTouchEvent(event)) {  
            return true;  
        }  
    }  
    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
        switch (event.getAction()) {  
            case MotionEvent.ACTION_UP:  
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
                    // take focus if we don't have it already and we should in  
                    // touch mode.  
                    boolean focusTaken = false;  
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
                        focusTaken = requestFocus();  
                    }  
                    if (!mHasPerformedLongPress) {  
                        // This is a tap, so remove the longpress check  
                        removeLongPressCallback();  
                        // Only perform take click actions if we were in the pressed state  
                        if (!focusTaken) {  
                            // Use a Runnable and post this rather than calling  
                            // performClick directly. This lets other visual state  
                            // of the view update before click actions start.  
                            if (mPerformClick == null) {  
                                mPerformClick = new PerformClick();  
                            }  
                            if (!post(mPerformClick)) {  
                                performClick();  
                            }  
                        }  
                    }  
                    if (mUnsetPressedState == null) {  
                        mUnsetPressedState = new UnsetPressedState();  
                    }  
                    if (prepressed) {  
                        mPrivateFlags |= PRESSED;  
                        refreshDrawableState();  
                        postDelayed(mUnsetPressedState,  
                                ViewConfiguration.getPressedStateDuration());  
                    } else if (!post(mUnsetPressedState)) {  
                        // If the post failed, unpress right now  
                        mUnsetPressedState.run();  
                    }  
                    removeTapCallback();  
                }  
                break;  
            case MotionEvent.ACTION_DOWN:  
                if (mPendingCheckForTap == null) {  
                    mPendingCheckForTap = new CheckForTap();  
                }  
                mPrivateFlags |= PREPRESSED;  
                mHasPerformedLongPress = false;  
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                break;  
            case MotionEvent.ACTION_CANCEL:  
                mPrivateFlags &= ~PRESSED;  
                refreshDrawableState();  
                removeTapCallback();  
                break;  
            case MotionEvent.ACTION_MOVE:  
                final int x = (int) event.getX();  
                final int y = (int) event.getY();  
                // Be lenient about moving outside of buttons  
                int slop = mTouchSlop;  
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    // Outside button  
                    removeTapCallback();  
                    if ((mPrivateFlags & PRESSED) != 0) {  
                        // Remove any future long press/tap checks  
                        removeLongPressCallback();  
                        // Need to switch from pressed to not pressed  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                    }  
                }  
                break;  
        }  
        return true;  
    }  
    return false;  
}  

这里源码很多,但是我们只需要几行就可以啦,从第三个大的if语句看起

 if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))

也就是从这里看起,这就话的意思是目前该控件是否是可点击的,我们知道这里的button肯定是可点击的,即使是不可点击的ImageView,我们要是给其注册setOnClickListener()的话,它也就变得为可点击的啦,这里可以查看一下setOnClickListener()的源码

 public void setOnClickListener(OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

该if语句的意思就是如果控件是不可以点击的话,注册该监听事件后,就变得可以点击啦。
所以这时onTouchEvent函数是一定会返回true的,所以onTouch函数中的事件是完全都被执行的,但是此时的onclick也是被执行的,关键是我们我知道它是在哪里被调用,其实就是在onTouchEvent函数中的

if (!post(mPerformClick)) {  
                                performClick();  
                            }  

这里我们就要再去查看一下performClick()到底是怎么定义的啦,继续追根溯源,原来秘密在这里

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}  

首先mOnClickListener 是什么呀,按住ctrl+k,我们去找一找

public void setOnClickListener(OnClickListener l) {  
    if (!isClickable()) {  
        setClickable(true);  
    }  
    mOnClickListener = l;  
}  

突然有种豁然开朗的感觉,这里其实回调机制,OnClickListener不就是我们定义的内部类嘛,所以肯定不会为空呀,所以这时就开始执行performClick()的 mOnClickListener.onClick(this)我们自己写的onClick的函数体中内容。
所以明白了上述的原理之后,自定义Button控件,代码如下所示

package com.example.demo2;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

public class MyButton extends Button {

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

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

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case 0:
            System.out.println("000000000000000");
            break;
        case 1:
            System.out.println("111111111111111");
            break;
        case 2:
            System.out.println("222222222222222");
            break;
        default:
            break;
        }
        return true;
    }
}

在布局文件中使用该自定义MyButton 控件在MainActivity中定义其相应的是事件

package com.example.demo2;

import java.security.PublicKey;

import android.os.Bundle;
import android.app.Activity;
import android.support.v4.view.ViewPager;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn = (Button) findViewById(R.id.btn);
        btn.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    System.out.println("onTouch------ACTION_DOWN");
                    break;
                case MotionEvent.ACTION_UP:
                    System.out.println("onTouch------ACTION_UP");
                    break;
                case MotionEvent.ACTION_MOVE:
                    System.out.println("onTouch------ACTION_MOVE");
                    break;
                }
                return false;
            }
        });

        btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                System.out.println("哈哈哈");
            }
        });
    }

}

点击该按钮运行的结果如下所示

05-10 12:04:28.737: I/System.out(9319): onTouch------ACTION_DOWN
05-10 12:04:28.737: I/System.out(9319): 000000000000000
05-10 12:04:29.067: I/System.out(9319): onTouch------ACTION_MOVE
05-10 12:04:29.067: I/System.out(9319): 222222222222222
05-10 12:04:29.067: I/System.out(9319): onTouch------ACTION_UP
05-10 12:04:29.067: I/System.out(9319): 111111111111111

这里是因为当onTouch返回false的时候,(根据源码)会调用onTouchEvent函数,但是此时我们重写了父类的该函数,所以会调用自己的onTouch函数,这里就是多态的思想啦,onTouch返回的为true,所以会把所有事件消耗完,又因为我们在源码中是先判断的 mOnTouchListener.onTouch(this, event)) ,所以该函数中相应的up、down、move都要比onTouchEvent中的要早,由于子类重写了onTouchEvent函数,所以onclick是永远都不会执行到了的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值