ViewGroup和View以及TouchDelegate的触摸事件源码简析

参考:

浅尝安卓事件分发机制

http://blog.csdn.net/zhaizu/article/details/50489398

深入Android开发之--理解View#onTouchEvent
https://my.oschina.net/banxi/blog/187267?p=1

Android 触摸消息处理

http://blog.csdn.net/siobhan/article/details/8257334

public boolean dispatchTouchEvent(MotionEvent ev) {
    ......

    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;

    //down事件来时,清理之前的各种状态
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Throw away all previous state when starting a new touch gesture.
        // The framework may have dropped the up or cancel event for the previous gesture
        // due to an app switch, ANR, or some other state change.
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }

    //ViewGroup节点是否要处理本次触摸事件,down事件或者mFirstTouchTarget不为空,则要检查子节点是否不允许父节点打断时间流程
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        //子节点未设置不允许父节点打断流程,则调用onInterceptTouchEvent询问父节点是否要自己处理该事件
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            intercepted = false;
        }
    } else {
        //当前不是down事件,并且在之前的down事件中,没有子节点来处理事件,所以move和up都由当前viewgroup来处理
        intercepted = true;
    }

    //当前ViewGroup不处理该事件,且为down事件,则尝试找一个子节点处理
    if (!canceled && !intercepted) {
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            final int childrenCount = mChildrenCount;
            if (newTouchTarget == null && childrenCount != 0) {
                //找到一个触摸位置在该chile里的节点
                final View[] children = mChildren;
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final View child = children[i];
                    if(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)){
                        mFirstTouchTarget = target;
                        alreadyDispatchedToNewTouchTarget = true;
                    }
                }
            }
        }
    }

    //找不到处理该消息的子节点,则由当前节点处理
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
    } else {
        // Dispatch to touch targets, excluding the new touch target if we already
        // dispatched to it.  Cancel touch targets if necessary.
        //由mFirstTouchTarget继续处理剩余事件
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                handled = true;
            }
        }
    }

    return handled;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
    //默认都为false
    return false;
}


//如果child为空,则由当前的ViewGroup调用它作为View的dispatchTouchEvent,否则调用child的dispatchTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;
    final MotionEvent transformedEvent = MotionEvent.obtain(event);
    // Perform any necessary transformations and dispatch.
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        //坐标变换到child的空间
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

//是否可见,并且是否没有在播放动画
private static boolean canViewReceivePointerEvents(@NonNull View child) {
    return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
            || child.getAnimation() != null;
}

protected boolean isTransformedTouchPointInView(float x, float y, View child,
        PointF outLocalPoint) {
    final float[] point = getTempPoint();
    point[0] = x;
    point[1] = y;
    transformPointToViewLocal(point, child);
    final boolean isInView = child.pointInView(point[0], point[1]);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(point[0], point[1]);
    }
    return isInView;
}

public void transformPointToViewLocal(float[] point, View child) {
    point[0] += mScrollX - child.mLeft;
    point[1] += mScrollY - child.mTop;

    if (!child.hasIdentityMatrix()) {
        child.getInverseMatrix().mapPoints(point);
    }
}

//View的dispatchTouchEvent的主要作用就是调用mOnTouchListener的onTouch和onTouchEvent

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

    final int actionMasked = event.getActionMasked();

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

    return result;
}

//view自身处理事件,将点击事件的回调通过post来执行,保证了先看到UI的变化,再执行逻辑
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        //disabled的View如果可点击,依然会吞噬事件
        return (((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
    }

    //如果有view设置了代理,则由代理view处理事件
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // 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)) {
                        performClick();
                    }

                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;
                setPressed(true, x, y);
                checkForLongClick(0, x, y);
                break;

            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                ......
                break;

            case MotionEvent.ACTION_MOVE:
                drawableHotspotChanged(x, y);

                // Be lenient about moving outside of buttons
                //是否移出了View之外
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        // Remove any future long press/tap checks
                        removeLongPressCallback();
                        setPressed(false);
                    }
                }
                break;
        }

        return true;
    }

    return false;
}


public boolean pointInView(float localX, float localY, float slop) {
    return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
            localY < ((mBottom - mTop) + slop);
}

private final class PerformClick implements Runnable {
    @Override
    public void run() {
        performClick();
    }
}

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

//TouchDelegate类可以让一个view的点击区域变大,以方便点击
//具体用法是,在当前view的父节点中设置TouchDelegate,传入当前view以及rect
//rect的求法是view.getHitRect(rect),表示view在父节点中的范围,再根据需要手动扩大rect
public class TouchDelegate {
    //需要点击区域变大的那个view
    private View mDelegateView;
    
    //变大后的点击区域
    private Rect mBounds;
    
    //在mBounds区域的基础上又考虑了slop
    private Rect mSlopBounds;
    
    /**
     * True if the delegate had been targeted on a down event (intersected mBounds).
     */
    //down事件是否在mSlopBounds区域内,表示可以传递给mDelegateView
    private boolean mDelegateTargeted;

    private int mSlop;

    //bounds一般为delegateView.getHitRect(bounds)在扩大一部分,delegateView为最终接收点击事件的view,一般为当前节点的子节点
    public TouchDelegate(Rect bounds, View delegateView) {
        mBounds = bounds;

        mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
        mSlopBounds = new Rect(bounds);
        mSlopBounds.inset(-mSlop, -mSlop);
        mDelegateView = delegateView;
    }

    //event为当前节点收到的事件,判断其是否在mBounds范围内,已决定是否传递给子节点delegateView
    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;
            //点击位置在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
                //如果判定为在mBounds或mSlopBounds范围内,则设置点击位置为delegateView的中心
                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;
    }
}
参考:
Android TouchDelegate   http://blog.csdn.net/a220315410/article/details/9141265
http://blog.csdn.net/tongcpp/article/details/23450975



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值