其实android的view事件传递一直很少关注源码的分析,这次在做项目的时候为了获取webview是否被点击了,如果被点击了,做一个逻辑的存储,所以会有下面的代码:

mWebView.setOnTouchListener(new View.OnTouchListener() {    @Override    public boolean onTouch(View v, MotionEvent event) {        //do something        return true;    }});

但是在网页里面又有js的按钮点击,此时呢,点击webview的按钮没有任何反应,所以此时,感觉是onTouch事件在搞鬼,因此,这里尝试着在在onTouch返回false尝试下,因此又有下面的代码:

mWebView.setOnTouchListener(new View.OnTouchListener() {    @Override    public boolean onTouch(View v, MotionEvent event) {        //do something        return false;    }});

因此这里想肯定是onTouch返回了true,导致onClick事件无法获取。猜想归猜想,还是得看下源码是怎么来定这个规则的,下面还是按照惯例,写个小的demo验证下:

public class TestView extends View {    private static final String TAG = TestView.class.getSimpleName();    public TestView(Context context) {        super(context);    }    public TestView(Context context, AttributeSet attrs) {        super(context, attires    }    public TestView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        int action = event.getAction();        String actionName = "";        if (action == MotionEvent.ACTION_DOWN) {            actionName = "action_down";        } else if (action == MotionEvent.ACTION_MOVE) {            actionName = "action_move";        } else if (action == MotionEvent.ACTION_UP) {            actionName = "action_up";        }        Log.d(TAG, "TestButton dispatchTouchEvent-----" + actionName);        return super.dispatchTouchEvent(event);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        int action = event.getAction();        String actionName = "";        if (action == MotionEvent.ACTION_DOWN) {            actionName = "action_down";        } else if (action == MotionEvent.ACTION_MOVE) {            actionName = "action_move";        } else if (action == MotionEvent.ACTION_UP) {            actionName = "action_up";        }        Log.d(TAG, "TestButton onTouchEvent-----" + actionName);        return super.onTouchEvent(event);    }}

testView记下了dispatchTouchEvent中三个action,以及返回super.dispatchTouchEvent(event),在ontouchEvent中同样记下了三个action,以及返回super.onTouchEvent(event)

public class EventActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {    private static final String TAG = EventActivity.class.getSimpleName();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        View testView = findViewById(R.id.test_view);        testView.setOnClickListener(this);        testView.setOnTouchListener(this);    }    @Override    public void onClick(View v) {        Log.d(TAG, "onClick-----" + v.getClass().getSimpleName());    }    @Override    public boolean onTouch(View v, MotionEvent event) {        int action = event.getAction();        String actionName = "";        if (action == MotionEvent.ACTION_DOWN) {            actionName = "action_down";        } else if (action == MotionEvent.ACTION_MOVE) {            actionName = "action_move";        } else if (action == MotionEvent.ACTION_UP) {            actionName = "action_up";        }        Log.d(TAG, "onTouch-----" + v.getClass().getSimpleName() + ";" + actionName);        return false;    }}

EventActivity里面分别设置了testViewonClickonTouch的监听,此时我们点击下testView,会发生如下的日志:

image.png
从日志上可以看出来先是触发了 testViewdispatchTouchEvent---> onTouch---> onTouchEvent---> onClick
用一张更详细的时序图可以表示如下:
image.png
下面再来验证开篇例子中 onTouch返回true的情况流程是咋样的:
image.png
仅仅改了这么一处,看下如下日志:
image.png

从这里可以看出来testViewontouchEvent事件和onClick事件都没有被执行,也就是说:
当view的setOnTouchListener的onTouch返回true的时候,testView的onTouchEvent和onClick事件都没有被触发

咋们再看两种情况:
testView.setEnabled(false)
代码事例:

image.png
日志如下:
image.png
从日志上看, 如果我们把view的enable标志设置成false,它的setOnTouchListener的onTouch方法不会被执行到,以及它的setOnClickListener的onClick方法也不会被执行到

testView.setClickable(false)
在该处设置的时候,需要注意该方法需要在testView.setOnClickListener(this)之后,在setOnClickListener之前调用是没有效果的,看下testView.setClickable(false)效果:

image.png
从日志上看,如果设置 setClickable(false)只会执行 dispatchTouchEventsetOnTouchListener的ontouchonTouchEventaction_down事件。

得到此结论后,咋们可以顺着源码找下去。
首先看下setOnTouchListener把该OnTouchListener给谁了:

image.png

ListenerInfo getListenerInfo() {    if (mListenerInfo != null) {        return mListenerInfo;    }    mListenerInfo = new ListenerInfo();    return mListenerInfo;}

这里获取了一个ListenerInfo对象,在ListenerInfo对象中封装了各种监听器,这里就不一一说了,然后给到它的mOnTouchListener属性,其实setOnClickListener也是同样的做法:

image.png
其实setOnClickListener就是将view的clickable属性至为true了,所以上面讲的例子中,为啥要把setClickable(false)放在了setOnClickListener之后才会起作用,并且将外面传过来的listener交给了ListenerInfo对象

既然上面从日志上看是先是触发了testView的dispatchTouchEvent那下面看下该方法:

public boolean dispatchTouchEvent(MotionEvent event) {    //省略代码    boolean result = false;    if (onFilterTouchEventForSecurity(event)) {        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {            result = true;        }        //noinspection SimplifiableIfStatement        ListenerInfo li = mListenerInfo;        //如果li.mOnTouchListener.onTouch(this, event)返回true,并且view的状态是enable状态下,该方法的result就直接返回true        if (li != null && li.mOnTouchListener != null                && (mViewFlags & ENABLED_MASK) == ENABLED                && li.mOnTouchListener.onTouch(this, event)) {            result = true;        }        //如果result=false,才会走下面的onTouchEvent        if (!result && onTouchEvent(event)) {            result = true;        }    }    //省略代码    return result;}

注释写得很清楚了,如果我们传入的OnTouchListener中的onTouch返回true的话,并且在enable=true情况下,就不会执行到onTouchEvent方法,直接返回了result=true,这也就验证了上面说的只会执行到dispatchTouchEventOnTouchListener中的onTouch方法。
还记得上面事例中我们设置过testview.setEnable(false)的时候,不会输出OnTouchListener.ontouch方法了没,因为在testview.setEnable(false)情况下此处的位与直接不成立,所以不会调用到OnTouchListener.ontouch方法。至于在testview.setEnable(false)OnClickLisener.onClick方法也没调用到,这个在后面的onTouchEvent回调中会讲到。
下面继续看onTouch返回false的情况,上面已经看到日志在action_up打印了下onClick的日志,咱们可以猜测onClick事件肯定是在onTouchEventACTION_UP调用的:

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;        //该处很关键,如果view不是enable状态下,直接返回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;        }    //如果是clickable才会往下走,否则ontouchEvent直接返回false    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {        switch (action) {            case MotionEvent.ACTION_UP:                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;                if ((viewFlags & TOOLTIP) == TOOLTIP) {                    handleTooltipUp();                }                if (!clickable) {                    removeTapCallback();                    removeLongPressCallback();                    mInContextButtonPress = false;                    mHasPerformedLongPress = false;                    mIgnoreNextUpEvent = false;                    break;                }                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {                    // take focus if we don't have it already and we should in                    // touch mode.                    boolean focusTaken = false;                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {                        focusTaken = requestFocus();                    }                    if (prepressed) {                        // The button is being released before we actually                        // showed it as pressed.  Make it show the pressed                        // state now (before scheduling the click) to ensure                        // the user sees it.                        setPressed(true, x, y);                    }                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {                        // This is a tap, so remove the long press check                        removeLongPressCallback();                        // Only perform take click actions if we were in the pressed state                        if (!focusTaken) {                            // 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)) {                                //调用onClick事件的地方                                performClick();                            }                        }                    }                    if (mUnsetPressedState == null) {                        mUnsetPressedState = new UnsetPressedState();                    }                    if (prepressed) {                        postDelayed(mUnsetPressedState,                                ViewConfiguration.getPressedStateDuration());                    } else if (!post(mUnsetPressedState)) {                        // If the post failed, unpress right now                        mUnsetPressedState.run();                    }                    removeTapCallback();                }                mIgnoreNextUpEvent = false;                break;        }        return true;    }    return false;}

上面注释写得很清楚在performClick中调用了OnClickListener中的onClick方法:

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;    }    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);    notifyEnterOrExitForAutoFillIfNeeded(true);    return result;}

还记得开篇事例中讲到了当enable=false,clickable=true的时候,没有调用OnClickListener的onClick了吧,因为在onTouch的时候
if ((viewFlags & ENABLED_MASK) == DISABLED)成立了,直接返回了clickable,所以不会继续往下走。
上面也分析了,如果view.clickable=false的时候,onTouchEvent直接返回false,那么直接返回给了dispatchTouchEvent了,此处涉及到viewgroup到view事件的传递,在下一篇会讲到如果onTouchEvent返回false,就不会调用action_move和action_up事件。

  • view中onTouchEvent直接返回true,看看日志如何

    image.png
    image.png
    这里可以看出来比默认返回super. onTouchEvent的情况少了onClick事件的回调,其实也很好理解,毕竟在没有调用super方法,肯定不会走里面的up回调的onClick事件。

  • view中onTouchEvent直接返回false,看看日志如何

    image.png
    image.png
    此处可以看到只走了action_down后面就没有了,可想而知,我们的onTouchEvent是view消费事件的核心,只要返回false什么都干不了,这里涉及到ViewGroup到View事件分发的原理,因此该篇先不做不过的陈述。

  • view中dispatchTouchEvent直接返回true,看看日志如何

    image.png
    image.png
    从日志上看,只执行了dispatchTouchEvent的三个action,因为没调用super. dispatchTouchEvent,所以其他的几个方法都不会被执行。

  • view中dispatchTouchEvent直接返回false,看看日志如何

    image.png
    image.png
    从日志上来看,只有dispatchTouchEvent的action_down也被触发了,该处也是ViewGroup到View事件的分发导致的,返回false,直接不让view的move和up事件继续往下走了。

总结

  • setClickable(false)需要在setOnClickListener之后调用才起作用,
    因为在setOnClickListener中将view设置setClickable(true)
  • 如果设置了setEnabled(true)setClickable(false),view的setOnTouchListeneronTouch会被触发,但是到了ontouchEvent的时候直接返回了false,那么dispatchTouchEvent也直接返回false,此时所有的事件都只有action_down,onClick事件也不会被执行到,关于为什么在onTouchEvent返回false只有action_down事件,会在viewgroup到view事件传递中讲到
  • 如果设置了setEnabled(false)setClickable(false),view的setOnTouchListeneronTouch不会被触发,此时onTouchEvent事件直接返回false,关于为什么在onTouchEvent返回false只有action_down事件,会在viewgroup到view事件传递中讲到,并且此时onClick事件也不会被触发
  • 如果设置了setEnabled(false)setClickable(true),view的setOnTouchListeneronTouch不会被触发,此时onTouchEvent事件直接返回true,onTouchEvent的action都会被触发到,但是onClick事件是不会被触发到,所以一般如果不想让view的onClick事件被触发到,可以直接设置view. setEnabled(false)
  • 如果view的setOnTouchListeneronTouch方法返回true,view的onTouchEvent和view的onClick事件都不会被执行
  • view设置的setOnClick事件是在onTouchEvent方法的action_up中执行的
  • view的onTouchEvent事件如果直接返回true,事件相当于消费了,onClick事件不会被执行到,因为onClick是在super.onTouchEvent中的action_up中被执行的
  • view的onTouchEvent事件如果直接返回false,事件相当于传给了它的ViewGroup,这个留到viewGroup到view事件传递的时候讲,现在只需要记住就行
  • view的dispatchTouchEvent直接返回true,事件由于没有调用到super.dispatchTouchEvent,因此后续的setOnTouchListeneronTouch方法不会被执行,onTouchEvent也不会被执行到,以及setOnClickListener中的onClick方法也不会被执行
  • view的dispatchTouchEvent直接返回false,事件就交给了viewgroup,这个留到viewGroup到view事件传递的时候讲,现在只需要记住就行

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. android HorizontalScrollView实现滚动状态监听
  3. android 开发 View _1_ View的子类们 和 视图坐标系图
  4. android 左右滑动+索引图标实现方法与代码
  5. Android: Keyguard分析之一:开机流程篇
  6. Android发送邮件的方法实例详解
  7. Android(安卓)多个listview监听item的点击事件
  8. android 模拟back键
  9. 32、详解Android(安卓)shape的使用方法

随机推荐

  1. Android(安卓)Contacts的使用(一)
  2. Android开发要看的网站(不断更新中)
  3. 使用Eclipse开发Android时整个工程或第三
  4. Android常用布局之LinearLayout(线性布局
  5. 麦子学院Android应用开发工程师视频教程
  6. Android(安卓)Studio开发环境的搭建
  7. Android中的人脸检测(静态和动态)
  8. Android(安卓)状态栏通知Notification(转
  9. Android(安卓)的属性系统
  10. Android(安卓)Handler机制8之消息的取出