美文网首页知识点Android开发经验谈Android开发
20 点击事件、触摸事件、enabled、clickable的关

20 点击事件、触摸事件、enabled、clickable的关

作者: 凤邪摩羯 | 来源:发表于2021-03-15 09:58 被阅读0次

    直接来总结

    附上本人总结的表格(已用代码验证)

    enabled clickable onTouchListener是否会被调用 onTouchListener#onTouch返回值 onClickListener是否会被调用 onTouchEvent返回值 dispatchTouchEvent返回值
    true true true true true
    true true false true true
    true false true true true
    true false false false false
    false true - true true
    false false - false false
    • 只要viewenabledonTouchListener就会被调用
    • 只有viewenabled且可点击(clickablelongClickablecontextClickable任意一种),且onTouchListener返回falseonClickListener才会被调用
    • 如果onTouchListener没有消耗事件,且是可点击的(三种情况),那么无论设不设置onClickListener也无论其返回值是什么,此事件都会被消耗,因为ViewonTouchEvent对事件进行了默认处理

    不设置onTouchListeneronClickListener相当于返回false
    dispatchTouchEvent返回值是为了分析父子View事件分发,表示触摸事件是否被消费

    分析

    触摸事件(TouchEvent)分发的起始点为Activity#dispatchTouchEvent(MotionEvent event),上一层就是Native层了

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        //先给Window处理,里面再给DecorView处理,再是ViewGroup,然后分发给各个View或ViewGroup
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //如果没有人处理,最后自身的onTouchEvent作为兜底事件
        return onTouchEvent(ev);
    }
    
    

    onTouchListener与onTouchEvent以及enable的影响

    View#dispatchTouchEvent(MotionEvent event)中有如下代码

    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
        //如果view是enabled,才会调用mOnTouchListener
        result = true;
    }
     //如果调用mOnTouchListener的onTouch返回true,onTouchEvent不会被调用
    if (!result && onTouchEvent(event)) {
        //如果view不是enabled或者mOnTouchListener的onTouch返回false,onTouchEvent被调用
        result = true;
    }
    
    

    小结

    onTouchEvent作为兜底的触摸事件处理操作,无论view是不是enabled,此处都会调用,
    但~~ 是,如果有mOnTouchListener并且其onTouch返回true 则表示该触摸事件已经被处理完了,则不会调用onTouchEvent

    onTouchEvent与onClickListener以及clickable的影响

    View#onTouchEvent(MotionEvent event)中有如下代码

    //clickable、longClickable、contenxtClickable都视为可点击
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        ......
        // 如果view不是enabled的
        // 如果是可点击的(上述三种情况),那么返回true,表示该事件被消费,但是不会真的响应它(不会调用onClickListener);
        //如果不可点击,那么返回false,表示该事件没有被消费
        return clickable;
    }
    ......
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                //当事件为UP时才是点击
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    handleTooltipUp();
                }
                //如果view不可点击(三种情况),那么就会直接返回false
                if (!clickable) {
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                //如果view是clickable执行到这里
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    ......
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        ......
                        if (!focusTaken) {
                            //使用一个Runnable的对象post到队列中来执行点击事件,其内部就是执行performClickInternal()方法
                            //这是为了在单击操作之前,视图的其他视觉状态会更新(点击效果)
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }
                    ......
                }
                mIgnoreNextUpEvent = false;
                break;
            case MotionEvent.ACTION_DOWN:
                ......
                break;
            case MotionEvent.ACTION_CANCEL:
                ......
                break;
            case MotionEvent.ACTION_MOVE:
                ......
                break;
        }
        return true;
    }
    return false;
    
    

    View#performClickInternal()又调用了View#performClick()方法,其部分代码如下

    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        //如果mOnClickListener存在就调用其onClick方法,返回true,表示点击事件被消费
        //但是这里的返回几乎没啥用,没有人使用它的返回值
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }
    
    

    小结

    • 如果view不是enabled的,那么不会调用onClickListenerview是否可点击(clickablelongClickablecontextClickable仅仅作为返回结果表示触摸事件是否被消费
    • 如果viewenabled
      • 如果view可点击,那就会调用其onClickListener,返回true表示触摸事件被点击监听器消费了
      • 如果view不可点击,直接返回false,表示触摸事件没有被消费

    如果纰漏,欢迎指正

    相关文章

      网友评论

        本文标题:20 点击事件、触摸事件、enabled、clickable的关

        本文链接:https://www.haomeiwen.com/subject/rfesfltx.html