View触摸事件分发

首先我们确定下一般View的几种事件触发方法:

dispatchTouchEvent
onTouchListener
onTouchEvent
onClickListener

首先我们来一个示例,重写了Button,代码如下:

public class MyButton extends Button
{
    private static final String TAG = MyButton.class.getSimpleName();

    public MyButton(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        int action = event.getAction();

        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event)
    {
        int action = event.getAction();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "dispatchTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(event);
    }
}

MainActivity的代码如下:

MyButton btn = (MyButton)findViewById(R.id.button_test);
        btn.setOnTouchListener(new View.OnTouchListener()
        {
            @Override
            public boolean onTouch(View v, MotionEvent event)
            {
                int action = event.getAction();

                switch (action)
                {
                    case MotionEvent.ACTION_DOWN:
                        Log.e(TAG, "onTouch ACTION_DOWN");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        Log.e(TAG, "onTouch ACTION_MOVE");
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.e(TAG, "onTouch ACTION_UP");
                        break;
                    default:
                        break;
                }

                return false;
            }
        });
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "OnClickListener ACTION_CLICK");
            }
        });

重写了Button的onTouchEvent、dispatchTouchEvent,在MainActivity中设置了OnTouchListener、OnClickListener。

日志显示的顺序是dispatchTouchEvent、OnTouchListener、onTouchEvent、最后是OnClickListener。
以下是view的dispatchTouchEvent方法代码:

if (event.isTargetAccessibilityFocus()) {....}
        if (mInputEventConsistencyVerifier != null) {.....}
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }
         //判断是否被其它窗口遮挡
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //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;
            }
        }
        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

actionMasked如果是按下事件则暂停View的滚动,如果当前view在滚动则停止滚动,接着判断当前的view是否需要消费事件,这里消费场景特定为是否处于窗口最前端,确认之后有三个过程:
1、是否正在滚动,若正在滚动则跳过事件处理
2、是否有Touch事件的监听,如果有则传入监听的Touch事件
3、传递给自己的onTouchEvent

这三个过程中如果有消费掉事件则停止事件的继续分发。其中onFilterTouchEventForSecurity是用来判断当前是否在窗口最前端,代码如下:

if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // 窗口被遮挡,消费该事件
            return false;
        }
        return true;

下面我们看下onTouchEvent是如何处理按键事件的:

        //  view被设置为disabled时候,touch事件照样被执行,但是不响应
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }

        //触摸事件代理
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

ACTION_UP操作:

boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
    boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
    focusTaken = requestFocus();
    }

if (prepressed) {
    setPressed(true, x, y);
    }

if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
    removeLongPressCallback();
   if (!focusTaken) {
       if (mPerformClick == null) {
           mPerformClick = new PerformClick();
           }
       if (!post(mPerformClick)) {
           performClick();
           }
      }
  }

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

  if (prepressed) {
      postDelayed(mUnsetPressedState,
      ViewConfiguration.getPressedStateDuration());
  } else if (!post(mUnsetPressedState)) { 
      mUnsetPressedState.run();
     }
     removeTapCallback();
 }
 mIgnoreNextUpEvent = false;

首先判断是否有PFLAG_PRESSED或者是PFLAG_PREPRESSED的状态;接着获取焦点操作。
如果是PFLAG_PREPRESSED的状态则刷新标志位。
mHasPerformedLongPress是表示是否触发了长按操作
mIgnoreNextUpEvent是否忽略UP事件
如果两个都为false,则执行删除检测长按的回调,如果当前焦点在自身上则,执行点击事件的触发,其中会通知OnClickListener。
后续的mUnsetPressedState是为了重置view的状态,比如按下状态的改变,延迟时间为64ms。最后删除TapCallback,下文会介绍,这是一个分辨滚动的回调操作。

ACTION_DOWN操作

mHasPerformedLongPress = false;
//鼠标右键则不消费此事件,直接结束
if (performButtonActionOnTouchDown(event)) {
    break;
   }
   // 判断是否在滚动的容器中
boolean isInScrollingContainer = isInScrollingContainer();
// 如果在滚动容器中,则延迟按下的反馈,因为可能是滚动事件
if (isInScrollingContainer) {
   mPrivateFlags |= PFLAG_PREPRESSED;
   if (mPendingCheckForTap == null) {
       mPendingCheckForTap = new CheckForTap();
       }
       mPendingCheckForTap.x = event.getX();
       mPendingCheckForTap.y = event.getY();
       postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
   } else {
       // 没有处于滚动容器中,则立刻反馈按下
       setPressed(true, x, y);
       checkForLongClick(0, x, y);
   }

首先mHasPerformedLongPress置为false,因为还没有检测到长按事件,接下来是判断是否在可滚动的容器中,如果是,则首先会把标志位设为PFLAG_PREPRESSED,接下来会有一个延迟执行的操作,时间为ViewConfiguration.getTapTimeout(),100ms。CheckForTap的代码为:

private final class CheckForTap implements Runnable {
        public float x;
        public float y;

        @Override
        public void run() {
            mPrivateFlags &= ~PFLAG_PREPRESSED;
            setPressed(true, x, y);
            checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
        }
}

也就是说如果100ms之后还是按下的状态则取消掉PFLAG_PREPRESSED状态,并且反馈按下事件并且标志位刷新为PFLAG_PRESSED,如果有长按事件也一并检测。

ACTION_MOVE操作

case MotionEvent.ACTION_MOVE:
    drawableHotspotChanged(x, y);
    if (!pointInView(x, y, mTouchSlop)) {
        removeTapCallback();
        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
            removeLongPressCallback();
            setPressed(false);
        }
    }

首先更新当前触摸的(x,y)坐标,如果未出界的话则执行removeTapCallback,这是干啥的呢,之前在DOWN事件中有一个延迟处理,如果100ms如果出界的话,则删除回调。若有长按的回调也一并删除,并重置按键状态为false。

以上是view的事件分发,但是其实在使用的过程中最底层view本身的事件判断是最后执行的,它的事件来源都是上层下发的,具体的流程如下图:
布局层次图
这里ViewGroup重写了View的dispatchTouchEvent 事件,并分发给子view,具体如何分发可以自己看源码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值