前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >View 事件分发

View 事件分发

作者头像
Yif
发布2019-12-26 15:07:35
5860
发布2019-12-26 15:07:35
举报
文章被收录于专栏:Android 进阶Android 进阶
undefined
undefined

setEnabled()与setClickable()区别

setEnabled设置为true,相当于激活事件,对触摸产生反应,而设置成false。

无论是否设置setClickable为false 还是true都不可点击,无法响应事件.

代码语言:javascript
复制
Button click = findViewById(R.id.click);
//click.setClickable(false);
//click.setEnabled(true);
//设置false无法响应任何点击事件
//click.setEnabled(false);        
click.setOnClickListener(this);
//只有在onClickListener事件之后设置clickable为false才可以不能点击,不响应事件,否则之前设置还是可以点击,但是设置false之后,但是还会产生一闪一闪变化        click.setClickable(false);

View的dispatchTouchEvent源码分析

代码语言:javascript
复制
public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }
 
    boolean result = false;
 
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }
 
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }
//onFilterTouchEventForSecurity判断当前View是否被遮盖
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
//li对象不会为空,但mOnTouchListener对象是不是为空根据该控件是否注册类setOnTouchListener监听
        if (li != null && li.mOnTouchListener != null
//如果控件不是enabled的那么onTouch方法就不会执行
                && (mViewFlags & ENABLED_MASK) == ENABLED
//mOnTouchListener.onTouch是否返回true,根据onTouch方法是否返回为true有关
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
//如果上面都成立,此时result为true,那么下面onTouchEvent就不会执行,否则只要上面有一个不成立,并且onTouchEvent
//返回true就会执行result=true方法,如果onTouchEvent返回false就不执行,dispatchTouchEvent就返回false,
//dispatchTouchEvent返回结果与上面代码与下面代码是否成立有关
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
 
    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
 
    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }
 
    return result;
}

dispatchTouchEvent源码分析总结:

  1. 任何触摸事件都是从dispatchTouchEvent方法开始进行分发的
  2. 首先执行onTouchListener方法,后执行onTouchEvent方法,也就是onTouch方法优先于onClick方法执行,原因由于onClick方法在onTouchEvent方法之调用performClick获取,而onTouchListener方法优先于onTouchevent方法,所以onTouch优先于onClick方法执行
  3. 当onTouch返回false,或者onTouchListener返回false就是控件没有设置setOnTouchListener方法,或者控件enabled属性为false,就执行onTouchEvent方法,否则不执行
  4. 当不是enabled,也就是说setEnabled(false)设置了onTouch方法也不执行,只能通过重写onTouchEvent方法,最后dispatchEvent方法返回值也就是onTouchEvent方法返回值
  5. 当控件是enabled,也就是说setEnabled(true)并且onTouch方法返回true,那么dispatchEvent就返回true,而onTouchEvent方法就不会执行

View 的onTouchEvent源码

代码语言:javascript
复制
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
 
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//如果当前view是disabled并且可以点击则消费事件
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return clickable;
    }
    if (mTouchDelegate != null) {
//这里调用了TouchDelegate类中的onTouchEvent方法,该方法是dispatchEvent返回为true时,才返回true,此时
onTouchEvent就返回true,将事件交给代理者处理
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
//如果view可以点击并且长按则一定返回true
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
//抬起事件
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
//判断是否是长按事件
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    handleTooltipUp();
                }
//没有设置长按事件点击
                if (!clickable) {
                    removeTapCallback();
//移除长按点击runnable
                    removeLongPressCallback();
                    mInContextButtonPress = false;
//设置长按点击false
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_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 (prepressed) {
                        // The button is being released before we actually
                        // showed it as pressed.  Make it show the pressed
                        // state now (before scheduling the click) to ensure
                        // the user sees it.
                        setPressed(true, x, y);
                    }
 
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // 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)) {
//在ACTION_UP中调用了onClick监听
                                performClick();
                            }
                        }
                    }
 
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
 
                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
 
                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;
 
            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
//表示长按事件还未触发
                mHasPerformedLongPress = false;
//clickable为false,或者没有设置长按事件
                if (!clickable) {
//检查长按事件监听
                    checkForLongClick(0, x, y);
                    break;
                }
//返回true,表示actiondowm事件被消费
                if (performButtonActionOnTouchDown(event)) {
                    break;
                }
 
                // Walk up the hierarchy to determine if we're inside a scrolling container.
                boolean isInScrollingContainer = isInScrollingContainer();
 
                // For views inside a scrolling container, delay the pressed feedback for
                // a short period in case this is a scroll.
                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
//发送延迟消息,时间为100毫秒,mPendingCheckForTap是CheckForTap实现了runnable接口
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // Not inside a scrolling container, so show the feedback right away
//设置当前控件按下状态并提供触摸坐标动画提示
                    setPressed(true, x, y);
//检查长按事件
                    checkForLongClick(0, x, y);
                }
                break;
 
            case MotionEvent.ACTION_CANCEL:
//判断控件是否设置clickable或者是否是长按事件
                if (clickable) {
                    setPressed(false);
                }
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;
 
            case MotionEvent.ACTION_MOVE:
                if (clickable) {
//当前view视图中的点发生改变时,才有必要传入到被视图管理的drawable与子视图中
                    drawableHotspotChanged(x, y);
                }
//如果用户手指移出控件,就移除所有的监听,移除Action_down设置的检测与长按
                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    // Remove any future long press/tap checks
                    removeTapCallback();
//去除标示,刷新背景
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false);
                    }
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                }
                break;
        }
 
        return true;
    }
 
    return false;
}
private void checkForLongClick(int delayOffset, float x, float y) {
//判断是否是长按事件监听
    if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
        mHasPerformedLongPress = false;
 
        if (mPendingCheckForLongPress == null) {
            mPendingCheckForLongPress = new CheckForLongPress();
        }
        mPendingCheckForLongPress.setAnchor(x, y);
        mPendingCheckForLongPress.rememberWindowAttachCount();
        mPendingCheckForLongPress.rememberPressedState();
//发送一个延迟消息
        postDelayed(mPendingCheckForLongPress,
                ViewConfiguration.getLongPressTimeout() - delayOffset);
    }
}
private void handleTooltipUp() {
    if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
        return;
    }
//移除消息队列中特殊的runnable
    removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
//发送延迟消息,延迟1500毫秒
    postDelayed(mTooltipInfo.mHideTooltipRunnable,
            ViewConfiguration.getLongPressTooltipHideTimeout());
}
//在ACTION_UP中调用
public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    //控件只要onclickListener不为空,也就是该控件调用了setOnClickListener监听,result就返回true否则返回false
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    notifyEnterOrExitForAutoFillIfNeeded(true);
    return result;
}
//控件调用了setOnClickLisener监听,如果控件是disclickable就设置clickable为true
public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

TouchDelegate中的onTouchEvent源码

代码语言:javascript
复制
public boolean onTouchEvent(MotionEvent event) {
    int x = (int)event.getX();
    int y = (int)event.getY();
    boolean sendToDelegate = false;
    boolean hit = true;
    boolean handled = false;
 
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
        Rect bounds = mBounds;
//只有点击的在该控件区域时,才会触发action_down事件
        if (bounds.contains(x, y)) {
            mDelegateTargeted = true;
            sendToDelegate = true;
        }
        break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_MOVE:
        sendToDelegate = mDelegateTargeted;
        if (sendToDelegate) {
            Rect slopBounds = mSlopBounds;
            if (!slopBounds.contains(x, y)) {
                hit = false;
            }
        }
        break;
    case MotionEvent.ACTION_CANCEL:
        sendToDelegate = mDelegateTargeted;
        mDelegateTargeted = false;
        break;
    }
    if (sendToDelegate) {
        final View delegateView = mDelegateView;
 
        if (hit) {
            // Offset event coordinates to be inside the target view
            event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
        } else {
            // Offset event coordinates to be outside the target view (in case it does
            // something like tracking pressed state)
            int slop = mSlop;
            event.setLocation(-(slop * 2), -(slop * 2));
        }
//只有view的dispatchEvent返回为true,handled才返回true
        handled = delegateView.dispatchTouchEvent(event);
    }
    return handled;
}

onTouchEvent源码分析总结:

  1. onTouchEvent会在ACTION_UP手势方法中调用performClick()方法,performClick()中调用了onClick方法,触发onClick监听
  2. 当dispatchEvent事件分发时,只有前一个action返回为true时,后一个action才会触发

View事件分发面试问题

事件分发中的 onTouch 和 onTouchEvent 有什么区别,该如何使用

这两个方法都是在 View 的 dispatchTouchEvent 中调用的,onTouch 优先于 onTouchEvent 执行。如果在 onTouch 方法中通过返回 true 将事件消费掉,onTouchEvent 将不会再执行。 onTouch 执行需要满足两个条件:

  • mOnTouchListener 的值不能为空
  • 当前点击的控件必须是 enable 的。因此如果你有一个控件是非 enable 的,那么给它注册 onTouch 事件将永远得不到 执行。对于这一类控件,如果我们想要监听它的 touch 事件,就必须通过在该控件中重写 onTouchEvent 方法来实现,相关代码块如下:
代码语言:javascript
复制

if (onFilterTouchEventForSecurity(event)) {         
//noinspection SimplifiableIfStatement        
 ListenerInfo li = mListenerInfo;        
 if (li != null && li.mOnTouchListener != null                
 && (mViewFlags & ENABLED_MASK) == ENABLED                 
&& li.mOnTouchListener.onTouch(this, event)) {            
 result = true;        
}         
if (!result && onTouchEvent(event)) {             
result = true;         
}     
}

onTouch 和onClick区别

onTouch事件要先于onClick事件执行,onTouch在事件分发方法dispatchTouchEvent中调用,而onClick在事件处理方法onTouchEvent中被调用,onTouchEvent要后于dispatchTouchEvent方法的调用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年7月19日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • setEnabled()与setClickable()区别
  • View的dispatchTouchEvent源码分析
    • dispatchTouchEvent源码分析总结:
    • View 的onTouchEvent源码
      • TouchDelegate中的onTouchEvent源码
        • onTouchEvent源码分析总结:
        • View事件分发面试问题
          • 事件分发中的 onTouch 和 onTouchEvent 有什么区别,该如何使用
            • onTouch 和onClick区别
            相关产品与服务
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档