Android之View的TouchDelegate,你真的理解事件分发了吗???

有人问我 事件分发是分发给View的吗?

我笃定的说是的,结果不是的。


其实View有一个TouchDelegate,可以给他设置这样一个东西,来使得你的事件触摸范围会被增大!简直牛X!


这个类的全部源码及部分注释

public class TouchDelegate {

    /**
     * View that should receive forwarded touch events
     */
    private View mDelegateView;

    /**
     * Bounds in local coordinates of the containing view that should be mapped to the delegate
     * view. This rect is used for initial hit testing.
     */
    private Rect mBounds;//你要扩大的这个新区域

    /**
     * mBounds inflated to include some slop. This rect is to track whether the motion events
     * should be considered to be be within the delegate view.
     */
    private Rect mSlopBounds;//给mBounds稍微扩大点,其目的是消除触摸误差

    /**
     * True if the delegate had been targeted on a down event (intersected mBounds).
     */
    private boolean mDelegateTargeted;

    /**
     * The touchable region of the View extends above its actual extent.
     */
    public static final int ABOVE = 1;

    /**
     * The touchable region of the View extends below its actual extent.
     */
    public static final int BELOW = 2;

    /**
     * The touchable region of the View extends to the left of its
     * actual extent.
     */
    public static final int TO_LEFT = 4;

    /**
     * The touchable region of the View extends to the right of its
     * actual extent.
     */
    public static final int TO_RIGHT = 8;

    private int mSlop;

    /**
     * Constructor
     *
     * @param bounds Bounds in local coordinates of the containing view that should be mapped to
     *        the delegate view
     * @param delegateView The view that should receive motion events
     */
    public TouchDelegate(Rect bounds, View delegateView) {//构造函数:rect是实际触摸区域,view是要修改触摸区域的view
        mBounds = bounds;

        mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();//这里就是设定一个值,如果你的滑动距离超过这个值,那么这一次就是有效滑动
        mSlopBounds = new Rect(bounds);
        mSlopBounds.inset(-mSlop, -mSlop);//就是上面讲的溢出区域,防止触摸误差
        mDelegateView = delegateView;
    }

    /**
     * Will forward touch events to the delegate view if the event is within the bounds
     * specified in the constructor.
     *
     * @param event The touch event to forward
     * @return True if the event was forwarded to the delegate, false otherwise.
     */
    public boolean onTouchEvent(MotionEvent event) {//这里是核心代码了:如果你落在这个rect区域内,就判定你是落在这个view内了,view自然就可以获取这次事件
        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区域小来进行的一个防止误差,所以你触摸到了rect,不一定有效
                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);//如果说是在rect区域内,那么就把event的位置修改在view的中心,模拟触摸事件是真的落在了view内了
            } 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;
    }
}

看看view是如何使用TouchDelegate的,在它的onTouchEvent里,首先就进行了一个判断

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

但是实战的时候,又发现不是这么一回事了,我的理解完全出现了偏差

其真正含义是,当你触摸一个view的时候,而且touch delegate的矩形区域也在这个view里,你触摸到了这块区域,那么这个view就可以把事件转发给delegate类中维护的那个view


实战演练

Android之View的TouchDelegate,你真的理解事件分发了吗???



代码

重写了activity的onWindowFousChanged

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    TextView tv = (TextView) root.getChildAt(0);
    tv.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(MainActivity.this, "666", Toast.LENGTH_SHORT).show();
        }
    });

    Rect rect = new Rect();
    rect.top = tv.getTop() - 100;
    rect.bottom = tv.getBottom() + 100;
    rect.left = tv.getLeft() - 100;
    rect.right = tv.getRight() + 100;

    root.setTouchDelegate(new TouchDelegate(rect, tv));
}