onTouchListener中的onTouch函数

来源:互联网 发布:网络直播基本设备 编辑:程序博客网 时间:2024/06/05 02:39

今天来研究一下onTouch函数的返回值。

首先在一个ImageView中添加一个监听器:

mImageView.setOnTouchListener(new View.OnTouchListener() {    @Override    public boolean onTouch(View v, MotionEvent event) {        Log.i(TAG, "onTouch " + event.getAction());        return false;    }});

代码很简单,我们仅打印了一下event的getAction()值,然后默认返回了一个false。
在这里返回true或false有何意义呢?SDK文档是这样写的:

True if the listener has consumed the event, false otherwise.

返回true说明监听函数消费了这次touch事件,返回false向上层说明监听没有消费这次事件。

实际文档这样说还是不明白:怎么叫消费,是否消费会造成什么影响?
用实践来检验,分别返回true与false运行程序进行实验:

返回值 Log true 手指接触view->在view上移动->抬起,一直打印getAction日志 false 手指接触view打印ACTION_DOWN;在view上移动->抬起,无日志打印


看起来返回值会对listener的调用产生影响,由于在onTouch中直接打印日志,那么表明返回false则会导致后续产生Touch事件根本不会调用onTouch函数

这是为何?只能看源码来寻求答案了。
首先由注册onTouchListener方法得知,onTouch肯定是在View类中被调用的。首先查找到listener被注册到何处:

public void setOnTouchListener(OnTouchListener l) {    // onTouchListener被赋值到View中getListenerInfo().mOnTouchListener    getListenerInfo().mOnTouchListener = l;}

接下来找到mOnTouchListener.onTouch() 这样的语句,在View的dispatchTouchEvent 中:

if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED        && li.mOnTouchListener.onTouch(this, event)) {    return true;}

在API 23上,dispatchTouchEvent 函数共计51行,而对比API 19,此函数代码仅22行。API 23上源码中诸如stopNestedScroll 函数是API 21才引入的,而onTouch事件的响应在新老版本上保持一致,因此研究API 19的代码就可以了,新特性的处理暂不涉及。

在API 19 中,dispatchTouchEvent的源码如下:

public boolean dispatchTouchEvent(MotionEvent event) {    // 根据源码注释,InputEventConsistencyVerifier仅用于Debug    if (mInputEventConsistencyVerifier != null) {        mInputEventConsistencyVerifier.onTouchEvent(event, 0);    }    if (onFilterTouchEventForSecurity(event)) {        //noinspection SimplifiableIfStatement        ListenerInfo li = mListenerInfo;        if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED                && li.mOnTouchListener.onTouch(this, event)) {            return true;        }        if (onTouchEvent(event)) {            return true;        }    }    if (mInputEventConsistencyVerifier != null) {        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);    }    return false;}

代码很短,可以很快浏览完。其中InputEventConsistencyVerifier 类在源码中注释:

Checks whether a sequence of input events is self-consistent. Logs a description of each problem detected.
When a problem is detected, the event is tainted. This mechanism prevents the same error from being reported multiple times.

可以看到这个类主要是检验输入事件的完整性的,对View类没有实质性影响。
另外一个onFilterTouchEventForSecurity通过源码注释可以判断在应用前台时均会返回true。

public boolean onFilterTouchEventForSecurity(MotionEvent event) {    //noinspection RedundantIfStatement    if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0            && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {        // Window is obscured, drop this touch.        return false;    }    return true;}

那么对观察现象产生影响的只可能是中间的两个if判断了。

在此结合dispatch函数整理一下点击ImageView时的逻辑,
从上至下先进入我们设置的onTouchListener判断中,再进入View的onTouchEvent函数中,因此:

onTouchListener返回值 onTouchEvent dispatch返回值 true 不会处理 true false 未知 未知


由我们目前得到的数据,在listener返回false时,后两项确实是未知的。因此还要验证一下View中的onTouchEvent到底会返回什么。

验证的方法不难:创建一个子类继承ImageView,仅覆写onTouchEvent函数,打印super.onTouchEvent 的返回值。

public class MyImageView extends ImageView {    private static final String TAG = "MyImageView";    public MyImageView(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        boolean upperOnTouch = super.onTouchEvent(event);        Log.i(TAG, "super ImageView onTouchEvent return: " + upperOnTouch);        return upperOnTouch;    }}

在listener返回false时,运行代码打印log:

06-28 17:26:59.235 3939-3939/com.example.gesturetest I/MyImageView: super ImageView onTouchEvent return: false

也就是说目前为止,View中的onTouchEvent没有对我们listener的返回产生影响(通过查看ImageView可以发现其中并没有覆写View的onTouchEvent方法,因此可以去掉ImageView的影响了)。上面的表可以更新为:

onTouchListener返回值 onTouchEvent dispatch返回值 true 不会处理 true false false false


目前为止的总体结论:

onTouchListener返回值 Log 说明(所测试ImageView) true 手指接触view->在view上移动->抬起,一直打印getAction日志 处理了过程中所有的Touch事件 false 手指接触view打印ACTION_DOWN;在view上移动->抬起,无日志打印 仅处理了ACTION_DOWN事件,后面的所有事件都未处理


可以推断,假设ImageView中onTouch返回true,上层的调用者拿到true后,后续再有事件,就会继续把事件传递给这个ImageView;若ImageView中onTouch返回了false,则上层后续不会再把Touch事件传递给ImageView了。
那么这个上层明显就是ImageView在XML中的上级节点了。假设ImageView布局写在LinearLayout中,那么传递给ImageView事件的上层就是LinearLayout。因此最终需要查看ViewGroup类的源码。
ViewGroup实际继承于View,因此也有dispatchTouchEvent函数,不过已经将其完全覆写。
在ViewGroup中查找子ViewdispatchTouchEvent 函数的调用,发现是在dispatchTransformedTouchEvent 函数中,ViewGroup需要将接收到的事件进行转换(例如坐标);再进一步查找dispatchTransformedTouchEvent 的调用,发现就是在ViewGroup的dispatchTouchEvent 中。
整体流程大致为:

Created with Raphaël 2.1.0??????ViewGroupViewGroupView(Child)View(Child)dispatchTouchEvent()dispatchTransformedTouchEvent()dispatchTouchEvent()onTouchListener.onTouch()onTouchEvent()returnreturn

在这里推测是dispatchTouchEvent 函数中的逻辑进行了处理。dispatchTransformedTouchEvent 函数声明是:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,            View child, int desiredPointerIdBits)


因此调用dispatchTransformedTouchEvent 是需要指定View的。如果是在这个函数中处理,当我们的ImageView不再接收任何Touch事件时,每次Touch事件都需要调用dispatchTransformedTouchEvent,效率就会降低。从这个角度,猜测真正是在dispatchTouchEvent 中进行了处理。
接下来整理ViewGroup中dispatchTouchEvent 的逻辑,首先会判断这个事件是否是一个ACTION_DOWN,如果是ACTION_DOWN,表明用户刚刚点击屏幕,是一系列Touch事件的起始,因此会调用cancelAndClearTouchTargetsresetTouchState 复位状态。

// Handle an initial 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拦截Touch事件的逻辑:

// Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN        || mFirstTouchTarget != null) {    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    if (!disallowIntercept) {        intercepted = onInterceptTouchEvent(ev);        ev.setAction(action); // restore action in case it was changed    } else {        intercepted = false;    }} else {    // There are no touch targets and this action is not an initial down    // so this view group continues to intercept touches.    intercepted = true;}


整个流程大致为:

Created with Raphaël 2.1.0ifACTION_DOWN ?允许Intercept ?intercepted = onInterceptTouchEvent()Endintercepted = falseFirstTouchTarget不为空yesnoyesnoyesno
0 0
原创粉丝点击