View的事件分发机制

事件分发机制3连

Activity的事件分发机制(https://blog.csdn.net/u013728021/article/details/102826314)
ViewGroup事件分发机制(https://blog.csdn.net/u013728021/article/details/102831095)
View的事件分发机制(https://blog.csdn.net/u013728021/article/details/102826446)

自定义View的套路

  1. 自定义属性
  2. onMeasure用于测量控件的大小
  3. onDraw用于绘制内容
  4. onTouchEvent用于用户交互

自定义ViewGroup的套路

  1. 自定义属性
  2. onMeasure for循环测量子View的宽高,最后计算自己的宽高
  3. onLayout 用于摆放子View,前提是View不是Gone的情况
  4. onDraw用于绘制自己,默认是不调用,如果要绘制就复写dispatchDraw
  5. onTouchEvent用于用户交互

View的两个重要方法

  1. dispatchTouchEvent(MotionEvent event) 负责事件分发,事件是从这个方法开始分发的。
  2. onTouchEvent(MotionEvent event) 触摸事件。

dispatchTouchEvent(MotionEvent event)源码分析

public boolean dispatchTouchEvent(MotionEvent event) {
   
    boolean result = false;

    if (onFilterTouchEventForSecurity(event)) {
        // ListenerInfo 封装事件监听的对象,里面封装了一系统列的事件监听回调对象
        // 只要是设置了监听事件,这个对象就不为空。
        ListenerInfo li = mListenerInfo;
        
      // 如果给View设置了OnTouchListener,
      // 控件是ENABLED状态,
      // 并且onTouch方法返回true,就不会调用onTouchEvent方法 
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED // 控件是enabled
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        // 如果没有设置OnTouchListener,或者onTouch返回false用户没有消费事件
        // 那么就会走View的onTouchEvent方法
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    return result;
}

如果View没有设置OnTouchListener,或者设置OnTouchListener且onTouch返回false,用户没有消费事件,那么就会走View的 onTouchEvent(event)方法

onTouchEvent(MotionEvent event)源码分析

    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        //此处说明view自己实现的onTouchEvent不支持多点,支持多点的会使用getActionMasked()
        final int action = event.getAction();
		//是否是点击事件 CLICKABLE:点击  LONG_CLICKABLE:长按  CONTEXT_CLICKABLE:android版本的上下文对象
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        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.
            //如果是一个可点击的View,被设置disable,可以把事件消费点,防止事件再传递,如条目布局中的View被设置disable后,点击该View不会把事件在传回item.
            return clickable;
        }
        if (mTouchDelegate != null) {
        	//增大我们的点击范围,防止View过小,现在基本没啥用途,应该通过UI设计解决
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
		//8.0新加的TOOLTIP,解释当前的View是干什么的,可以在View中设置 android:tooltipText=""设置,长按View会出现提示""设置的提示信息,所以这里和clickable平级“或”的关系
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                	//摸到标记位的flag置空,就是设置成不再摸到标记位
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    	//设置一个延时器,多少时间之后TOOLTIP不再显示
                        handleTooltipUp();
                    }
                    if (!clickable) {
                    	//不可点击进行各种状态移除
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = 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;
                        //如果 可以获取焦点 并且 在触控模式下获取焦点(如EditText) 并且 没有焦点  
                        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)) {
                                    performClickInternal();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                        	//为了用户体验
                        	//预按下的时候,64ms的延迟后再设置为未按下状态。
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;
				//主要在ACTION_DOWN拦截掉事件,后面的事件也会交给该控件
                case MotionEvent.ACTION_DOWN:
                	//是不是摸到屏幕了(因为android有实体按键,按键会被模拟成手指按下了)
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                    	//如果不是clickable,检查长按
                        checkForLongClick(0, x, y);
                        break;
                    }
					//检测鼠标右键点击
                    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();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                    	//不在滑动控件中
                        // Not inside a scrolling container, so show the feedback right away	
                        //设置按下的状态
                        setPressed(true, x, y);
                        //开始对长按就行检查,和上面滑动控件中处理的等待时间不同,这里是0
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;
                    
				//滑动事件
                case MotionEvent.ACTION_MOVE:
                	//如果可以点击
                    if (clickable) {
                    	//5.0的波纹效果
                        drawableHotspotChanged(x, y);
                    }

                    // 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;
    }

是否是鼠标右键点击

	/**
     * Performs button-related actions during a touch down event.
     * 是否是鼠标右键点击
     * @param event The event.
     * @return True if the down was consumed.
     *
     * @hide
     */
    protected boolean performButtonActionOnTouchDown(MotionEvent event) {
        if (event.isFromSource(InputDevice.SOURCE_MOUSE) &&
            (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
            showContextMenu(event.getX(), event.getY());
            mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
            return true;
        }
        return false;
    }

是否是滑动控件,这里在自定义ViewGroup控件的时候如果不需要滑动,在 shouldDelayChildPressedState()应该返回false,让控件响应的更快

    /**
     * @hide
     * 是否是滑动控件
     */
    public boolean isInScrollingContainer() {
        ViewParent p = getParent();
        while (p != null && p instanceof ViewGroup) {
        	//是否应该延迟子View的按下事件,ViewGroup的shouldDelayChildPressedState()默认返回为true,
        	//这里在自定义ViewGroup控件的时候如果不需要滑动,在 shouldDelayChildPressedState()应该返回false,让控件响应的更快
            if (((ViewGroup) p).shouldDelayChildPressedState()) {
                return true;
            }
            p = p.getParent();
        }
        return false;
    }

预按下状态的处理

    //预按下状态的处理
    private final class CheckForTap implements Runnable {
        public float x;
        public float y;

        @Override
        public void run() {
        	//解除预按下状态
            mPrivateFlags &= ~PFLAG_PREPRESSED;
            //处理为按下的状态
            setPressed(true, x, y);
			//检测长按事件,此时等待时间变为:点击等待器的点击时间,getTapTimeout为100ms
            checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
        }
    }

检测长按点击事件

	//检测长按点击事件
	// int delayOffset 延迟检测的时间
    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);
        }
    }

检测手指是否移出点击范围,slop是一个溢出范围,在这个范围内不算溢出

	
   	/**
     * Utility method to determine whether the given point, in local coordinates,
     * is inside the view, where the area of the view is expanded by the slop factor.
     * This method is called while processing touch-move events to determine if the event
     * is still within the view.
     *
     * @hide
     */
    public boolean pointInView(float localX, float localY, float slop) {
        return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
                localY < ((mBottom - mTop) + slop);
    }

执行点击事件

public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        // 执行点击事件
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }
    return result;
}

点击和长按事件都在 View 的 onTouchEvent 里面
点击事件的处理是在 onTouchEvent ACTION_UP里面
长按事件的处理是在 onTouchEvent ACTION_DOWN里面
OnTouchListener的 onTouch 方法返回 false,事件执行顺序:
OnTouchListener–> onTouchEvent --> onClickListener

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值