Android记一次debug——TouchDelegate的坑

bug

黑色是button,最外部的是ViewGroup,给ViewGroup设置了一个TouchDelegate以此扩大button的点击区域。但是发现点击了额外多出来的点击区域后,ViewGroup就再也响应不了点击事件了。

我一开始猜测是业务的问题,压根没想到Delegate会有坑。于是看了好久的业务,实在定位不到原因所在。


最后不得已回顾了Delegate的源码,果然有问题

入口

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;

    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) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

点进去

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;

        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));
        }
        handled = delegateView.dispatchTouchEvent(event);
    }
    return handled;
}

注意这段代码

if (bounds.contains(x, y)) {
            mDelegateTargeted = true;
            sendToDelegate = true;
        }

点击button自然是不会把事件流转到view group的onTouchEvent里。点击额外的部分,这个时候就会把事件流转到view group里了,然后进行delegate的判断。进去之后,只要成功符点击了额外区域,也就是上面这段代码,一个成员变量mDelegateTargeted就会被保存未true。

继续看这段代码

sendToDelegate = mDelegateTargeted;
        if (sendToDelegate) {
            Rect slopBounds = mSlopBounds;
            if (!slopBounds.contains(x, y)) {
                hit = false;
            }
        }

这里会对sendToDelegat赋值为true。

最下面一段

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));
        }
        handled = delegateView.dispatchTouchEvent(event);
    }
    return handled;

这个判断就会进去了。

handled = delegateView.dispatchTouchEvent(event);

然后就把点击事件交给孩子,看看它要不要。我们这里没有对button进行特殊复写,而且他天生就是可点击的(事实上,大部分 view默认都是不可点击的,在设置了点击事件之后,就可点击了)。如果你仔细看过view的onTouchEvent的源码,你就会发现不管是ENABLE还是DISABLE,他都会返回clickable,所以可点击,就会返回true了。因此return handled等价于return true。

回到入口

if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

第二个if里的判断成功返回true了,所以view(这个view group)的onTouchEvent就返回true了,成功消费(不过这是一次 特殊的事件转移,算view group消费还是button消费,都可以)。

猜你喜欢

转载自blog.csdn.net/qq_36523667/article/details/81041408