Android之事件分发机制深入理解与详细解析(View)

要想很好的自定义view与viewgroup,那么对于事件分发机制,需要了解的非常透彻才能使用的游刃有余。

这里参考了郭大神的文章,并且对其中讲解的不太理解的地方,做了深入的探究和详细的解析。

这里先做简要的介绍:
一个动作,点击动作(按下+抬起),滑动动作(按下+滑动+抬起)
这样的一个动作,下文称为”大事件”。
一个动作(事件),是由很多的”小事件”序列组成的。
主要的小动作(小事件)就只有三种:down,move,up

一: view的事件分发

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;  //注意这里是false
    }  
});

setOnClickListener,setOnTouchListener两个方法都可以实现点击事件,而onTouchListener事件可以做更多的事,因为他有个参数MotionEvent 可以判断手指按下,抬起,移动。

下面仅仅是一个简单的点击,输出:

onTouch execute, action 0// 点下事件
onTouch execute, action 2// 移动事件
onTouch execute, action 1//抬起事件
onClick execute//最后触发点击事件,一个点击事件就是(按下+抬起)

下面是点击+移动+抬起事件,输出:

onTouch execute, action 0// 点下事件
onTouch execute, action 2// 移动事件
onTouch execute, action 2// 移动事件
onTouch execute, action 2// 移动事件
onTouch execute, action 1//抬起事件
onClick execute//最后触发点击事件,一个点击事件就是(按下+抬起)

总结一下:在一次事件传递中onTouch()是先执行,而onclick()是后执行的。

下面把onTouch事件返回值改成true:

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 true;  //注意这里是true
    }  
});

输出:

onTouch execute, action 0
onTouch execute, action 2
onTouch execute, action 1

或者

onTouch execute, action 0
onTouch execute, action 2
onTouch execute, action 2
onTouch execute, action 1

onClick方法没有没执行。
总结:onTouch方法返回true,代表这个action小事件被onTouch消费掉了,因而不会再继续向下执行。

普及一点知识:
任何一个view控件被触摸到,都会调用dispatchTouchEvent方法(dispatchTouchEvent是View的一个方法)。

//这里是dispatchTouchEvent部分代码
public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}  

1.mOnTouchListener != null//注册了onTouchListener事件
2.(mViewFlags & ENABLED_MASK) == ENABLED//当前点击的控件是否是enable的,所以的view默认都是enabled的,恒true
3.mOnTouchListener.onTouch(this, event)//onTouch返回true

当1,2,3条件都满足的时候,直接就返回true,事件就结束了,不会继续往下执行。(满足上面的实验,onTouch返回true,onClick就没执行)

那么,当onTouch返回false的时候,就会执行onTouchEvent(event)。

由此,onClick方法可想而知,就在onTouchEvent里面执行的。

public boolean onTouchEvent(MotionEvent event) {  
    final int viewFlags = mViewFlags;  
    if ((viewFlags & ENABLED_MASK) == DISABLED) {  
        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)) {  //如果view是可点击的
        switch (event.getAction()) {  
            case MotionEvent.ACTION_UP:  //此刻的事件为抬起手指
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  

                    boolean focusTaken = false;  
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
                        focusTaken = requestFocus();  
                    }  
                    if (!mHasPerformedLongPress) {  
                        removeLongPressCallback();  
                        if (!focusTaken) {  
                            if (mPerformClick == null) {  
                                mPerformClick = new PerformClick();  
                            }  
                            if (!post(mPerformClick)) {  
                                performClick();  //这里面执行的就是onClick方法(如果设置了onClick)
                            }  
                        }  
                    }  
                    if (mUnsetPressedState == null) {  
                        mUnsetPressedState = new UnsetPressedState();  
                    }  
                    if (prepressed) {  
                        mPrivateFlags |= PRESSED;  
                        refreshDrawableState();  
                        postDelayed(mUnsetPressedState,  
                                ViewConfiguration.getPressedStateDuration());  
                    } else if (!post(mUnsetPressedState)) {  
                        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();  
                int slop = mTouchSlop;  
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    removeTapCallback();  
                    if ((mPrivateFlags & PRESSED) != 0) {  
                        removeLongPressCallback();  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                    }  
                }  
                break;  
        }  
        return true;  //不管前面的actin是什么事件,都会执行到这里,返回true。
    }  
    return false;  
}  

1.view 可点击
2.事件是手指抬起
3.设置了onClickListener监听

满足三个条件,才会执行到onClick方法。

注意:如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action都不会传递到该dispatchTouchEvent。

明明在onTouch事件里返回了false,系统还是在onTouchEvent方法中帮你返回了true。就因为这个原因,才使得前面的例子中ACTION_UP可以得到执行。

(相信“注意”这一段话大家还是不太好理解,这里来做非常详细的实验)
自定义一个button,重写dispatchTouchEvent(MotionEvent event)方法,输出调用信息。

动作:点击一个VIEW,输出:

dispatchTouchEvent event=MotionEvent { action=ACTION_DOWN}
onTouch execute, action 0//小事件一,按下
dispatchTouchEvent event=MotionEvent { action=ACTION_MOVE}
onTouch execute, action 2//小事件二,移动
dispatchTouchEvent event=MotionEvent { action=ACTION_UP }
onTouch execute, action 1//小事件三,抬起

分析:
宏观的一个点击的”大事件”,实际这里发生了三个小事件,每个小事件都是从dispatchTouchEvent开始的。

动作:在View上滑动,输出:

dispatchTouchEvent event=MotionEvent { action=ACTION_DOWN}
onTouch execute, action 0//小事件一,按下
dispatchTouchEvent event=MotionEvent { action=ACTION_MOVE}
onTouch execute, action 2//小事件二,移动
dispatchTouchEvent event=MotionEvent { action=ACTION_MOVE}
onTouch execute, action 2//小事件三,移动
dispatchTouchEvent event=MotionEvent { action=ACTION_UP}
onTouch execute, action 1//小事件四,抬起

分析:
宏观的一个滑动的”大事件”,这里发生了四个以上的小事件,每个小事件都是从dispatchTouchEvent开始的。

这些看似分离的事件,实际上又是相互关联的。只有前一个小事件action返回true,后一个action小事件才会执行。

这里做一个实验,当小事件分发到第5个,dispatchTouchEvent返回false,看看后面的小事件还能不能继续执行。

  int count = 0;
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        count ++;
        Log.d("TAG","dispatchTouchEvent count = "+count+"   event="+event );
        if (count == 5) return false;//小事件执行到第5次就返回false
        boolean result = super.dispatchTouchEvent(event);
        return result;
    }

动作:在View上面滑动 ,输出:

 dispatchTouchEvent count = 1   event=MotionEvent { action=ACTION_DOWN}//返回true,第1个小事件正常(按下)
onTouch execute, action 0
dispatchTouchEvent count = 2   event=MotionEvent { action=ACTION_MOVE}//返回true,第2个小事件正常(滑动)
onTouch execute, action 2
dispatchTouchEvent count = 3   event=MotionEvent { action=ACTION_MOVE}//返回true,第3个小事件正常(滑动)
onTouch execute, action 2
dispatchTouchEvent count = 4   event=MotionEvent { action=ACTION_MOVE}//返回true,第4个小事件正常(滑动)
onTouch execute, action 2
dispatchTouchEvent count = 5   event=MotionEvent { action=ACTION_MOVE}//返回false,第5个小事件不执行(滑动)
dispatchTouchEvent count = 6   event=MotionEvent { action=ACTION_MOVE}//返回true,第6个小事件正常(滑动)
onTouch execute, action 2
dispatchTouchEvent count = 7   event=MotionEvent { action=ACTION_MOVE}//返回true,第7个小事件正常(滑动)
onTouch execute, action 2
dispatchTouchEvent count = 8   event=MotionEvent { action=ACTION_UP}//返回true,第8个小事件正常(抬起)
onTouch execute, action 1

可以看到,中途有一个count==5的时候,这个小事件返回了false。而接下来的小事件action,还是可以继续传递到dispatchTouchEvent 的。

第二种情况:

 @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        count ++;
        Log.d("TAG","dispatchTouchEvent count = "+count+"   event="+event );
        if (count == 1) return false;//小事件执行了1次就返回false
        boolean result = super.dispatchTouchEvent(event);
        return true;
    }

动作:任何动作, 输出:

dispatchTouchEvent count = 1   event=MotionEvent { action=ACTION_DOWN}

可以看到,第一个事件就返回了false ,而后续的所有的小事件action都不再传递到dispatchTouchEvent 了。

1.一开始就返回false的,后续action不在传递,也不会传递到dispatchTouchEvent 。
2.中途返回false的,后续action还会继续传递,dispatchTouchEvent 会接收到后续action小事件。

总结:一个大动作(大事件),由很多的小事件组成,这些小事件都是从最里面的层的传递过来,由于这里是view,也就是最外层(浅层)郭神的文章里“只有前一个小事件action返回true,后一个action小事件才会执行。”在我看来,这一句话不是完全准确的。

不相信,那再做测试:

重写Button

第一种情况:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        count ++;
        Log.d("TAG","dispatchTouchEvent count = "+count+"   event="+event );
        //if (count == 1) return false;//小事件执行了1次就返回false
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //return super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_DOWN" );
                return false;//这里返回false
            case MotionEvent.ACTION_MOVE:
                Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_MOVE" );
                return true;
            case MotionEvent.ACTION_UP:
                Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_UP" );
                return true;
        }
        return true;
    }

输出:

dispatchTouchEvent count = 5   event=MotionEvent 
onTouch execute, action 0
onTouchEvent MotionEvent.ACTION_DOWN

第二种情况:

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        //return super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_DOWN" );
                return true;
            case MotionEvent.ACTION_MOVE:
                Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_MOVE" );
                return false;//这里为false
            case MotionEvent.ACTION_UP:
                Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_UP" );
                return true;
        }
        return true;
    }

输出:

dispatchTouchEvent count = 83   event=MotionEvent { action=ACTION_DOWN
onTouch execute, action 0
onTouchEvent MotionEvent.ACTION_DOWN
dispatchTouchEvent count = 84   event=MotionEvent { action=ACTION_MOVE
onTouch execute, action 2
onTouchEvent MotionEvent.ACTION_MOVE
dispatchTouchEvent count = 85   event=MotionEvent { action=ACTION_MOVE
dispatchTouchEvent count = 86   event=MotionEvent 
onTouch execute, action 1
onTouchEvent MotionEvent.ACTION_UP

第三种情况:

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        //return super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_DOWN" );
                return true;
            case MotionEvent.ACTION_MOVE:
                Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_MOVE" );
                return true;
            case MotionEvent.ACTION_UP:
                Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_UP" );
                return false;//这里为false
        }
        return true;
    }

输出:

dispatchTouchEvent count = 83   event=MotionEvent { action=ACTION_DOWN
onTouch execute, action 0
onTouchEvent MotionEvent.ACTION_DOWN
dispatchTouchEvent count = 84   event=MotionEvent { action=ACTION_MOVE
onTouch execute, action 2
onTouchEvent MotionEvent.ACTION_MOVE
dispatchTouchEvent count = 85   event=MotionEvent { action=ACTION_MOVE
dispatchTouchEvent count = 86   event=MotionEvent 
onTouch execute, action 1
onTouchEvent MotionEvent.ACTION_UP

“只有前一个小事件action返回true,后一个action小事件才会执行。”这句话应该是:只有down小事件返回false,后续的小事件序列都不会再传递到这里。(最真切的说,应该是第一次进来的action小事件返回fasle,就会终止,只不过第一次进来的事件,肯定是down事件,说以可以直接说是 down事件)

有人是这样说的“如果是DOWN事件返回true,View所在的容器会记录当前DOWN事件被那个View消费了,下次容器派发MOVE事件直接调用这个View的dispatchTouchEvent,反之,如果当前view返回的是false,容器就会将事件派发给其他的View,下次就不会将MOVE事件派发给那个返回了false的view”

这里仅仅对imageview之onTouchListener

findViewById(R.id.btn).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent event) {
                Log.d("TAG", "onTouch execute, action " + event.getAction());
                return false;//
            }
        });

动作:点击或滑动,输出:

dispatchTouchEvent count = 2   event=MotionEvent { action=ACTION_DOWN}
onTouch execute, action 0

原因是:ImageView默认情况下是不可点击的。在onTouchEvent中,直接返回了false,使得下一个action也不能正常传递执行。当我们在为他设置onClickstener的时候,就变成可点击的了。

郭大神的事件分发机制博客里面有这样的总结,这里添加更详细的补充:
1.为什么图片轮播器里的图片使用Button而不用ImageView?

当时我在图片轮播器里使用Button,主要就是因为Button是可点击的,而ImageView是不可点击的。如果想要使用ImageView,可以有两种改法。
第一,在ImageView的onTouch方法里返回true,也就是dispatchEvent方法返回了true,这样可以保证ACTION_DOWN之后的其它action都能得到执行,才能实现图片滚动的效果。
第二,在布局文件里面给ImageView增加一个android:clickable=”true”的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,在onTouchEvent里面还是会返回true,ACTION_DOWN之后的其它action也是可以得到执行的。

2 .onTouch和onTouchEvent有什么区别,又该如何使用?

这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。
(onTouch起到是否事件分发的决定性作用,因为设置了onTouchListener,那么条件1就true,条件2任何view都是enabled的,恒true,条件三就是onTouch的返回值)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值