Android进阶知识树——View、ViewGroup事件分发机制详解

对于Android开发者来说,自定义View是必须攻克的一关,也是从初级工程师迈向高级的进阶关卡,要想通过此阶段,除了必须掌握View的测量、绘制、滑动等基础知识外,更要掌握View的核心知识点:View的事件分发,本篇就一起从源码的角度分析View和ViewGroup的事件分发机制;

1、View的事件分发

在我们平时的使用或写自定义View时,都会直接或间接的使用View的事件分发,View的事件分发主要与View源码中的3个方法有关:

  1. dispatchTouchEvent()
  2. onTouch()
  3. onTouchEvent()

下面我们针对这三个方法从源码学习和分析事件的分发,一起从本质上掌握View是如何在层层传递和消耗事件;

  • dispatchTouchEvent(MotionEvent event)
public boolean dispatchTouchEvent(MotionEvent event) {
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;
}
}

上面代码是dispatchTouchEvent()中的部分代码,也是与我们使用最接近的核心代码,首先会判断View是否设置触摸监听mOnTouchListener,如果设置则会调用OnTouchListener.onTouch()方法,如果此方法返回true,则dispatchTouchEvent()返回true即拦截事件,若onTouch()返回false,则调用onTouchEvent(),如果onTouchEvent()返回true则事件被消耗,否则事件继续传递;从上面的方法和叙述我们可以得出以下结论:

  1. 若View设置OnTouchListener,则先调用onTouch(),所以OnTouchListener的优先级高于onTouchEvent()
  2. 若onTouch()返回true,表示onTouch消耗事件,此时onTouchEvent()不会调用
  3. 若onTouch()返回false,此时onTouchEvent()被调用,若onTouchEvent返回true,事件被消耗

1.1、onTouchEvent()源码分析

  • ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
    mPrivateFlags3 |= PFLAG3_FINGER_DOWN; // 设置mPrivateFlags3为FINGER_DOWN标记
}
mHasPerformedLongPress = false;  //设置false表示此事还未出发长按事件

boolean isInScrollingContainer = isInScrollingContainer();  // 调用父容器的shouldDelayChildPressedState(),默认true

if (isInScrollingContainer) {
    mPrivateFlags |= PFLAG_PREPRESSED; // 状态设置为中间状态PFLAG_PREPRESSED
    if (mPendingCheckForTap == null) {
        mPendingCheckForTap = new CheckForTap();
    }
    mPendingCheckForTap.x = event.getX();
    mPendingCheckForTap.y = event.getY();
    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); // 延时发送执行CheckForTap中的run(),ViewConfiguration.getTapTimeout() = 100ms
} else {
    setPressed(true, x, y);
    checkForLongClick(0, x, y); // 直接检测长按事件
}

//CheckForTap中调用检测长按事件
@Override
public void run() {
    mPrivateFlags &= ~PFLAG_PREPRESSED;
    setPressed(true, x, y);
    checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);//调用长按检测方法
}

// checkForLongClick中延时发送CheckForLongPress实例
postDelayed(mPendingCheckForLongPress,
        ViewConfiguration.getLongPressTimeout() - delayOffset);  // getLongPressTimeout()为 500ms(系统默认的长按时间)

@Override
public void run() {
    if ((mOriginalPressedState == isPressed()) && (mParent != null)
            && mOriginalWindowAttachCount == mWindowAttachCount) {
        if (performLongClick(mX, mY)) {  // 
            mHasPerformedLongPress = true;  // 设置标志表示触发长按;此标志是否为true取决于li.mOnLongClickListener.onLongClick的返回值
        }
    }
}

//在performLongClick()中代码会最终调用performLongClickInternal()
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
    handled = li.mOnLongClickListener.onLongClick(View.this);  //调用长按监听中的onLongClick();返回值影响mHasPerformedLongPress
}

以上代码是View的onTouchEvent()的ACTION_DIOWN执行逻辑,只粘贴了部分关键代码,所执行逻辑如上面注释,下面我们逐步分析以下:

  1. 首先将mPrivateFlags3设置为FINGER_DOWN标记
  2. 将mHasPerformedLongPress设置为false,表示点击还未触发长按事件
  3. 创建CheckForTap()实例,并延时发送执行CheckForTap中的run()
  4. 在checkForLongClick中延时发送CheckForLongPress实例,检测长按事件
  5. 在performLongClick()中代码会最终调用performLongClickInternal(),performLongClickInternal回调设置的mO
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值