Android View 触摸机制

本文详细解析了Android中的触摸事件从接收开始到派发的全过程,包括ViewRootImpl如何处理输入事件、根view如何派发、ViewGroup与View的触摸事件派发规则、onTouchEvent的默认实现以及多点触摸的处理机制。通过对ACTION_DOWN、ACTION_UP、ACTION_POINTER_DOWN等事件的分析,揭示了触摸事件在View和ViewGroup间的传递细节,包括如何通过pointerId和pointerIndex追踪触摸点,以及如何利用GestureDetector和ScaleGestureDetector处理手势操作。
摘要由CSDN通过智能技术生成


View系统的触摸事件机制,主要是窗口接收触摸事件MotionEvent,并从根view开始,逐层递归的向子view派发该事件,并决定哪个view进行事件的响应的过程;处理时是先交由子view,子view未处理该事件,才会尝试自己处理事件。

整体派发流程图:
在这里插入图片描述

一.接收触摸事件开始派发

在ViewRootImpl中,维护了一个InputChannel对象,是客户端接收发送输入事件的对象;

还维护了一个继承自InputEventReceiver的WindowInputEventReceiver类的对象,用于窗口真正接收输入事件:

  1. 创建Receiver时传入InputChannel和当前Looper

  2. native层在发生输入事件时将会调用nativePollOnce方法,处理触摸事件并唤醒MessageQueue

  3. 处理时会调用到注册的Receiver的dispatchInputEvent,内部调用子类(WindowInputEventReceiver)实现的onInputEvent

  4. 最终调用到ViewPostImeInputStage的onProcess方法,从根view开始派发触摸事件

  5. InputStage类是一个处理输入事件的类,是一个链式的类(存有next),ViewRootImpl定义了多个子类处理不同阶段的处理过程,前面的未消耗掉事件时,才会到下一个stage处理:

比如在ViewPostImeInputStage之前有ImeInputStage对象,是处理输入法的,在输入法弹出且此次触摸事件要关闭输入法时,ImeInputStage就会消耗掉该事件,那么后续的ViewPostImeInputStage就不会处理该事件

//ViewRootImpl的setView()方法时初始化
 
if ((mWindowAttributes.inputFeatures
        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
   
    mInputChannel = new InputChannel();//初始化InputChannel
}
 
if (mInputChannel != null) {
   
    if (mInputQueueCallback != null) {
   
        mInputQueue = new InputQueue();
        mInputQueueCallback.onInputQueueCreated(mInputQueue);
    }
    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
            Looper.myLooper());//使用channel初始化Receiver
}
 
final class WindowInputEventReceiver extends InputEventReceiver {
   
    public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
   
        super(inputChannel, looper);
    }

    @Override
    public void onInputEvent(InputEvent event) {
   
        enqueueInputEvent(event, this, 0, true);//处理事件
    }

}
 
final class ViewPostImeInputStage extends InputStage {
   
    public ViewPostImeInputStage(InputStage next) {
   
        super(next);
    }

    @Override
    protected int onProcess(QueuedInputEvent q) {
   
        if (q.mEvent instanceof KeyEvent) {
   
            return processKeyEvent(q);
        } else {
   
            handleDispatchWindowAnimationStopped();
            final int source = q.mEvent.getSource();
            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
   
                return processPointerEvent(q);//处理触摸事件
            }...
        }
    }

    private int processPointerEvent(QueuedInputEvent q) {
   
        final MotionEvent event = (MotionEvent)q.mEvent;

        mAttachInfo.mUnbufferedDispatchRequested = false;
        boolean handled = mView.dispatchPointerEvent(event);//根view开始派发触摸事件,返回值代表是否处理了事件
        ...
        return handled ? FINISH_HANDLED : FORWARD;//是否处理决定后续(系统的一个默认处理)InputStage是否处理,貌似没啥影响。。。
    }
}

此处的mView就是根view,对于Activity的窗口来说就是Activity的根view—DecorView,其他窗口类似,都是指根view

下面以Activity为例说明触摸事件传递流程

二.根view派发触摸事件

Activity对于窗口的ViewRootImpl的根view是一个DecorView对象,所以开始派发时会调用到DecorView的dispatchPointerEvent方法,进而调用其dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent ev) {
   
    final Callback cb = getCallback();
    return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
            : super.dispatchTouchEvent(ev);//如果有callback调用callback的dispatchTouchEvent,否则调用自身的(ViewGroup)的dispatchTouchEvent方法
}

对于Activity来说,DecorView的callback就是Activity本身,所以会调用Activity的dispatchTouchEvent方法进行处理

public boolean dispatchTouchEvent(MotionEvent ev) {
   
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
   
        onUserInteraction();//DOWN事件回调该方法,给用户一次处理的机会
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
   //调用Window的superDispatchTouchEvent方法,如果window处理了本次事件则返回true
        return true;
    }
    return onTouchEvent(ev);//否则交给Activity的onTouchEvent方法进行处理,默认返回false未处理
}

getWindow()在这里就是PhoneWidnow,他的superDispatchTouchEvent方法直接调用DecorView的superDispatchTouchEvent方法,而DecorView的该方法直接调用super,即ViewGroup的superDispatchTouchEvent的方法

下面就从View层面来分析触摸事件传递机制

三.ViewGroup派发触摸事件

public boolean dispatchTouchEvent(MotionEvent ev) {
   
    ...

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
   
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        //DOWN事件来临时要清除之前的触摸状态,包括mFirstTouchTarget(当前处理触摸的子view)的重置以及相关view的CANCEL标志位等等
		//一般情况下在UP或CANCEL事件执行后会重置,这里还要手动重置是因为系统可能在切换APP,ANR,或其他情况下导致UP或CANCEL事件被抛弃,所以在每次DOWN事件时都要重置一下
        if (actionMasked == MotionEvent.ACTION_DOWN) {
   
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        //这一步是检测是否拦截的时机和处理
        final boolean intercepted;
		//如果是DOWN事件(一次触摸事件的开始,决定要不要自己处理),或者已经有处理触摸事件的子view(是否拦截其继续处理),则需要去判断是否拦截
        if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
   
			//该标志代表是否不允许拦截,ViewGroup默认没有该字段,都会尝试拦截;子View可以通过requestDisallowInterceptTouchEvent方法设置,后面会讲到该方法
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
   //可以尝试拦截
                intercepted = onInterceptTouchEvent(ev);//调用onInterceptTouchEvent方法,返回值为是否拦截了该事件
                ev.setAction(action); // restore action in case it was changed
            } else {
   
                intercepted = false;
            }
        } else {
   //不是DOWN事件,还没有处理的子view,就在此ViewGroup拦截处理
            intercepted = true;
        }

        ...

        //这一步是检查是否是应该cancel的事件
        final boolean canceled = resetCancelNextUpFlag(this)//View本身带有CANCEL标识(由系统的某些行为引起的View需要cancel的情况)
                || actionMasked == MotionEvent.ACTION_CANCEL;//本次触摸事件为CANCEL(系统行为比如焦点变换等引起的CANCEL事件)

        ...
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;//是否已经派发给相应的新target处理事件,下面用作去重
        if (!canceled && !intercepted) {
   
			...
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
   //连同上面的条件是:如果此次事件为DOWN事件且没有被cancel和拦截,那么就要找到处理事件的child
                ...

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
   
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    final ArrayList<View> preorderedList = buildOrderedChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();//可自定义遍历顺序
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
   //遍历children
                        final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值