Android中View的事件分发机制

本文详细解析了Android中View的事件分发机制,包括dispatchTouchEvent、onTouchEvent等方法的工作原理,以及点击事件如何在不同控件间传递。

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

今天学习了一下View的事件分发,受益匪浅。一直在想一个View的事件传递是怎么样的呢?今天就进行一探究竟:


1.首先写两个控件 : ImageView 和 Button. 我们都知道ImageView是不可以点击(click)的,而Button则默认可以点击.对ImageView 和 Button 分别设置setOnTouchListener().可以发现ImageView只执行一次OnTouch(),Button则执行了两次,而当把OnTouch()方法的返回值设置成return true;时两者都执行了两次。

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

原因是:android程序中当你去触摸控件时自动回执行ACTION_DOWN事件 当返回false时,只会响应down事件,可能不会响应up事件;当返回true时,说明可以返回down事件和up事件。是因为当返回true时,当前的事件可以被消费掉,从而传到下一个动作,如果当前的事件都不能被消费,传到后面也是白费的。


2.但是有人就会想为什么点击Button可以执行两次呢?这就要看源码了。

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

首先我们要知道触摸事件一定是先经过 disPatchTouchEvent() 这个方法去对这个事件进行分发,源码中可以看到如果满足单个条件就直接return true释放,如果其中一个条件不满足就return onTouchEvent(event); 这三个条件分别是1. mOnTouchListener != null 2. (mViewFlags & ENABLED_MASK) == ENABLED 3. mOnTouchListener.onTouch(this, event) 。 其中第一个条件中 mOnTouchListener看源码可以知道它就是我们前面setOnTouchListener()中new 出来的对象 所以肯定不为空,第二个条件是指当前的控件是不是enable当然是可以的啊默认就是所有控件enable,但是这句话在这里肯定有他的作用,那就是我们可以设置控件的enable状态,第三个条件则是一个回调啊也就是onTouch()方法里的返回值。这下子就明白了 onTouch()中返回false了才能进入onTouchEvent(event), 所以马上明白ImageView 执行一次 Button执行两次肯定跟这个法有关系。(同时可以证明先执行onTouch(), 后执行onTouchEvent())


3.我们再往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 (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {} 意思是当前控件是不是可以点击的或者可以长按的,往下看如果可以点击下面就有个ACTION_UP,fantastyBaBy!原来是这么回事啊,终于明白了为什么ImageView只是执行一次而Button执行了两次的原因吧 还有就是执行完执行默认返回true。


4.还没完呢 后来我又想到我碰到过ImageView可以设置setOnClickListener();结果一看里面的源码又发现 if (!isClickable()) { setClickable(true); } 。。。。这下彻底明白了吧 设置了setOnClickListener()之后如果不可点击就默认让它可以点击。


5.问题解决了我们还要注意一些地方:
1.就是OnTouch() 和 OnTouchEvent()方法肯定是Ontouch先执行的,前面讲了回调执行了OnTouch()返回值是false时不满足三个条件之一 就执行onTouchEvent()方法;
2.onTouch方法先于onClick()方法因为onTouch()比onTouchEvent先执行 而oclick()后执行于onTouchEvent();
6.好了 View的事件分发就讲到这里了,下次再说ViewGroup的事件分发。


补充:
1)public boolean dispatchTouchEvent(MotionEvent ev)—–这个方法用来分发TouchEvent
2)public boolean onInterceptTouchEvent(MotionEvent ev)—–这个方法用来拦截TouchEvent
3)public boolean onTouchEvent(MotionEvent ev)—–这个方法用来处理TouchEvent

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值