美文网首页
Android事件传递机制

Android事件传递机制

作者: 飞奔吧牛牛 | 来源:发表于2018-12-23 16:48 被阅读0次

    Android事件传递机制主要涉及到三个方法:dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent,分别是关于事件分发、事件拦截、事件处理的。涉及到的组件主要包括Activity,ViewGroup,View。
    需要注意的是,ViewGroup也是继承自View的,这里的View指的是没有子View的控件,比如TextView和Button。
    ViewGroup有上诉三个方法,但是Activity和View是没有onInterceptTouchEvent 方法的。原因也是很简单,当用户触摸屏幕的时候,Activity作为事件传递的顶点,如果它把事件拦截后,那么整个屏幕就没有反应了。View作为最底层的控件,它没有子View,不需要拦截。

    我认为要了解一个知识点还是需要自己去写一个Demo。
    思路:
    1.自定义layout继承LinearLayout,复写dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent方法。
    2.自定义View继承Button,复写dispatchTouchEvent,onTouchEvent,方法。
    3.在Activity中复写dispatchTouchEvent,onTouchEvent方法。

    MyLinearLayout,在三个事件方法中打印Log

    public class MyLinearLayout extends LinearLayout {
    
        String Tag = "TouchEventDemo";
    
        public MyLinearLayout(Context context) {
            super(context);
        }
    
        public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public MyLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(Tag, "MyLinearLayout: dispatch:action_down");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(Tag, "MyLinearLayout: dispatch:action_move");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(Tag, "MyLinearLayout: dispatch:action_up");
                    break;
            }
            boolean b = super.dispatchTouchEvent(ev);
            Log.e(Tag, "MyLinearLayout: superDispatch: " + b);
            return b;
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(Tag, "MyLinearLayout: intercept:action_down");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(Tag, "MyLinearLayout: intercept:action_move");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(Tag, "MyLinearLayout: intercept:action_up");
                    break;
            }
            boolean b = super.onInterceptTouchEvent(ev);
            Log.e(Tag, "MyLinearLayout: superIntercept: " + b);
            return b;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(Tag, "MyLinearLayout: onTouchEvent:action_down");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(Tag, "MyLinearLayout: onTouchEvent:action_move");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(Tag, "MyLinearLayout: onTouchEvent:action_up");
                    break;
            }
            boolean b = super.onTouchEvent(event);
            Log.e(Tag, "MyLinearLayout: superTouch: " + b);
            return b;
        }
    }
    

    MyButton,在事件方法中打印Log。

    
    public class MyButton extends Button {
        String Tag = "TouchEventDemo";
    
        public MyButton(Context context) {
            super(context);
        }
    
        public MyButton(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(Tag, "MyButton: dispatch:action_down");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(Tag, "MyButton: dispatch:action_move");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(Tag, "MyButton: dispatch:action_up");
                    break;
            }
            boolean b = super.dispatchTouchEvent(ev);
            Log.e(Tag, "MyButton: superDispatch: " + b);
            return b;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(Tag, "MyButton: onTouchEvent:action_down");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(Tag, "MyButton: onTouchEvent:action_move");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(Tag, "MyButton: onTouchEvent:action_up");
                    break;
            }
    
            boolean b = super.onTouchEvent(event);
            Log.e(Tag, "MyButton: superTouch: " + b);
            return b;
        }
    }
    

    MainActivity,在事件方法中打印Log

    使用上面自定义的两个类,设置setOnTouchListener和setOnclickListener方法,并在事件方法中打印Log。

    
    public class MainActivity extends AppCompatActivity {
    
        String Tag = "TouchEventDemo";
        private MyLinearLayout mLayout;
        private MyButton mBtn;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mLayout = ((MyLinearLayout) findViewById(R.id.layout));
            mBtn = ((MyButton) findViewById(R.id.btn));
            mLayout.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            Log.e(Tag, "MyLinearLayout:setOnTouchListener: action_down");
                            break;
                        case MotionEvent.ACTION_MOVE:
                            Log.e(Tag, "MyLinearLayout:setOnTouchListener: action_move");
                            break;
                        case MotionEvent.ACTION_UP:
                            Log.e(Tag, "MyLinearLayout:setOnTouchListener: action_up");
                            break;
                    }
                    return false;
                }
            });
    
            mBtn.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            Log.e(Tag, "MyButton:setOnTouchListener: action_down");
                            break;
                        case MotionEvent.ACTION_MOVE:
                            Log.e(Tag, "MyButton:setOnTouchListener: action_move");
                            break;
                        case MotionEvent.ACTION_UP:
                            Log.e(Tag, "MyButton:setOnTouchListener: action_up");
                            break;
                    }
                    return false;
                }
            });
             
            mLayout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.e(Tag, "layout is onClicked");
                }
            });
    
            mBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.e(Tag, "button is onClicked");
                }
            });
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(Tag, "Activity: dispatch:action_down");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(Tag, "Activity: dispatch:action_move");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(Tag, "Activity: dispatch:action_up");
                    break;
            }
            boolean b = super.dispatchTouchEvent(ev);
            Log.e(Tag, "Activity: superDispatch: " + b);
            return b;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(Tag, "Activity: onTouchEvent:action_down");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(Tag, "Activity: onTouchEvent:action_move");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(Tag, "Activity: onTouchEvent:action_up");
                    break;
            }
            boolean b = super.onTouchEvent(event);
            Log.e(Tag, "Activity: superOnTouchEvent: " + b);
            return b;
        }
    }
    

    打印这么多Log,其实就是在每一个方法中打印了行为(是按下:ACTION_DOWN,是移动:ACTION_MOVE,还是抬起:ACTION_UP)。目的就是要了解它的每一个动作是怎么传递下去的,以及已经方法之间的影响。

    先研究下MyButton的处理事件的行为是什么样子的

    一. 当一切默认的情况下,打印结果为:

    Activity: dispatch:action_down
    
        MyLinearLayout: dispatch:action_down
        MyLinearLayout: intercept:action_down
        MyLinearLayout: intercept return : false
        
            MyButton: dispatch:action_down
            MyButton:setOnTouchListener: action_down
            MyButton: onTouchEvent:action_down
            MyButton: onTouchEvent return : true
            MyButton: dispatch return : true
            
        MyLinearLayout: dispatch return : true
        
    Activity: dispatch return : true
    ---------------------------------------------
    Activity: dispatch:action_move
    
        MyLinearLayout: dispatch:action_move
        MyLinearLayout: intercept:action_move
        MyLinearLayout: intercept return : false
        
            MyButton: dispatch:action_move
            MyButton:setOnTouchListener: action_move
            MyButton: onTouchEvent:action_move
            MyButton: onTouchEvent return : true
            MyButton: dispatch return : true
            
        MyLinearLayout: dispatch return : true
        
    Activity: dispatch return : true
    ---------------------------------------------
    Activity: dispatch:action_up
    
        MyLinearLayout: dispatch:action_up
        MyLinearLayout: intercept:action_up
        MyLinearLayout: intercept return : false
        
            MyButton: dispatch:action_up
            MyButton:setOnTouchListener: action_up
            MyButton: onTouchEvent:action_up
            MyButton: onTouchEvent return : true
            MyButton: dispatch return : true
            
        MyLinearLayout: dispatch return : true
        
    Activity: dispatch return : true 
    
    button is onClicked
    
    总结:

    1.dispatchTouchEvent方法默认返回true
    2.onInterceptTouchEvent方法默认返回false
    3.onTouchEvent方法默认返回true
    4.事件默认到最小单位(MyButton)则会被他自己处理。

    二. 当MyButton的onTouchEvent不调用super.onTouchEvent,而是直接返回true的情况下,

    //        boolean b = super.onTouchEvent(event);
            boolean b = true;
            Log.e(Tag, "MyButton: onTouchEvent return : " + b);
            return b;
    

    打印结果为:

    Activity: dispatch:action_down
    
        MyLinearLayout: dispatch:action_down
        MyLinearLayout: intercept:action_down
        MyLinearLayout: intercept return : false
        
            MyButton: dispatch:action_down
            MyButton:setOnTouchListener: action_down
            MyButton: onTouchEvent:action_down
            MyButton: onTouchEvent return : true
            MyButton: dispatch return : true
            
        MyLinearLayout: dispatch return : true
        
    Activity: dispatch return : true
    ---------------------------------------------
    Activity: dispatch:action_move
    
        MyLinearLayout: dispatch:action_move
        MyLinearLayout: intercept:action_move
        MyLinearLayout: intercept return : false
        
            MyButton: dispatch:action_move
            MyButton:setOnTouchListener: action_move
            MyButton: onTouchEvent:action_move
            MyButton: onTouchEvent return : true
            MyButton: dispatch return : true
            
        MyLinearLayout: dispatch return : true
        
    Activity: dispatch return : true
    ---------------------------------------------
    Activity: dispatch:action_up
    
        MyLinearLayout: dispatch:action_up
        MyLinearLayout: intercept:action_up
        MyLinearLayout: intercept return : false
        
            MyButton: dispatch:action_up
            MyButton:setOnTouchListener: action_up
            MyButton: onTouchEvent:action_up
            MyButton: onTouchEvent return : true
            MyButton: dispatch return : true
            
        MyLinearLayout: dispatch return : true
        
    Activity: dispatch return : true
    
    总结:

    唯一的影响就是MyButton的onClick方法不执行了。
    查看源码:

    //super(也就是View)的onTouchEvent方法中,有处理关于ACTION_UP的逻辑。其中会回调用onclick()方法。
    public boolean onTouchEvent(MotionEvent event) {
            if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
                switch (action) {
                    case MotionEvent.ACTION_UP:
                          ...
                                    if (!post(mPerformClick)) {
                                        performClick();
                                    }
                          ...
                          break;
                }
            }
        
    }
    
     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;
        }
    

    onClick方法是在动作抬起后执行的。
    虽然都是返回的ture,但是由于没有调用super.onTouchEvent方法,后续的onClick事件就得不到调用了。

    三. 当MyButton的onTouchEvent直接返回false的情况下,

    //        boolean b = super.onTouchEvent(event);
            boolean b = false;
            Log.e(Tag, "MyButton: onTouchEvent return : " + b);
            return b;
    

    打印结果为:

    Activity: dispatch:action_down
    
        MyLinearLayout: dispatch:action_down
        MyLinearLayout: intercept:action_down
        MyLinearLayout: intercept return : false
        
            MyButton: dispatch:action_down
            MyButton:setOnTouchListener: action_down
            MyButton: onTouchEvent:action_down
            MyButton: onTouchEvent return : false
            MyButton: dispatch return : false
            
        MyLinearLayout:setOnTouchListener: action_down
        MyLinearLayout: onTouchEvent:action_down
        MyLinearLayout: onTouchEvent return: true
        MyLinearLayout: dispatch return : true
        
    Activity: dispatch return : true
    ---------------------------------------------
    Activity: dispatch:action_move
    
        MyLinearLayout: dispatch:action_move
        MyLinearLayout:setOnTouchListener: action_move
        MyLinearLayout: onTouchEvent:action_move
        MyLinearLayout: onTouchEvent return: true
        MyLinearLayout: dispatch return : true
        
    Activity: dispatch return : true
    ---------------------------------------------
    Activity: dispatch:action_up
    
        MyLinearLayout: dispatch:action_up
        MyLinearLayout:setOnTouchListener: action_up
        MyLinearLayout: onTouchEvent:action_up
        MyLinearLayout: onTouchEvent return: true
        MyLinearLayout: dispatch return : true
        
    Activity: dispatch return : true
    
    layout is onClicked
    
    总结:

    1.MyButton,onTouchEvent方法在down时返回false,则其dispatch方法返回false,代表它以后不会对事件进行分发了。之后父布局(MyLinearLayout)拿到这个false,并记录这个false,直接调用了自己的onTouchEvent。并在发生move和up事件时也直接调用自己的onTouchEvent,这时候这个父布局相当于最底层的View。类似于之前MyButton的地位。也就是说没有onInterceptedTouchEvent方法了(可以这么理解)。
    2.MyButton在Action_down的时候返回false,之后的move和up事件就不会被继续分发给它了。
    3.由于事件交给了MyLinearLayout来处理,action_down之后,它的interceptTouchEvent方法已经不再执行,onTouchEvent和onClick方法得到了执行。
    说明:在1.中为什么说onTouchEvent方法返回false,则dispatch方法返回false呢?
    源码中有这样的一段:

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

    意思就是:

    if(
    1.在外部设置了onTouchListener(是的,我们之前在MainActivity中设置了) 
    && 2.控件处于可用状态(也没错,现在是Enable状态)
    && 3.onTouchListener.onTouch方法返回true(我们返回的的是false))
    {
          //这里最后一个条件不符合,
          return  true;
    }
    //所以继续向下执行。
    if(onTouchEvent) {
          return true;
    }
    

    所以,onTouchEvent方法返回false,则dispatch方法返回false。

    总结:onTouchEvent方法,返回true,表示事件已经被消费。返回false,则将事件交给父布局来处理。

    再研究下MyLinearLayout处理事件的行为是什么样子的

    测试onInterceptTouchEvent方法。
    该方法调用super.onInterceptTouchEvent方法和直接返回false

    //   boolean b = super.onInterceptTouchEvent(ev);
            boolean b = false;
            Log.e(Tag, "MyLinearLayout: intercept return : " + b);
            return b;
    

    结果都是一样的

    MyLInearLayout 的onInterceptTouchEvent方法返回true

    打印结果:

    Activity: dispatch:action_down
        MyLinearLayout: dispatch:action_down
            MyLinearLayout: intercept:action_down
            MyLinearLayout: intercept return : true
                MyLinearLayout:setOnTouchListener: action_down
                MyLinearLayout: onTouchEvent:action_down
                MyLinearLayout: onTouchEvent return: true
        MyLinearLayout: dispatch return : true
    Activity: dispatch return : true
    ---------------------------------------------
    Activity: dispatch:action_move
        MyLinearLayout: dispatch:action_move
            MyLinearLayout:setOnTouchListener: action_move
            MyLinearLayout: onTouchEvent:action_move
            MyLinearLayout: onTouchEvent return: true
        MyLinearLayout: dispatch return : true
    Activity: dispatch return : true
    ---------------------------------------------
    Activity: dispatch:action_up
        MyLinearLayout: dispatch:action_up
            MyLinearLayout:setOnTouchListener: action_up
            MyLinearLayout: onTouchEvent:action_up
            MyLinearLayout: onTouchEvent return: true
        MyLinearLayout: dispatch return : true
    Activity: dispatch return : true
    
    layout is onClicked
    
    总结:

    1.当拦截方法返回true时。事件会直接交给自己的onTouchEvent方法。
    2.当拦截方法再action_down返回true后,之后的move和up事件就不会再去执行该拦截方法了。

    MyLInearLayout 的onTouchEvent方法也和MyButton一样。

    总结:

    以上诉Demo为例。
    当点击button时,事件会先传到LienarLayout中,调用LienarLayout的dispatchTouchEvent()方法,这时,会先检测以前有没有子控件的dispatchTouchEvent方法有没有返回false,如果没有,再调用onInterceptTouchEvent()方法,
    1.如果onInterceptTouchEvent返回true,则调用自己的onTouchEvent()方法。
    2.如果onInterceptTouchEvent返回false,则调用button的dispatchTouchEvent()方法,button的dispatchTouchEvent()方法会调用自己的onTouchEvent()方法,onTouchEvent()方法返回true,则dispatchTouchEvent()方法返回true,事件被消费,反之dispatchTouchEvent()方法返回false。LinearLayout的dispatchTouchEvent()方法拿到这个false后,会直接调用自己的onTouchEvent()方法。
    注意,不是因为MyButton的onTouchEvent方法返回false而直接调用MyLinearLayout的onTouchEvent方法。而是onTouchEvent导致dispatchTouchEvent返回了false,然后让MyLinearLayout的dispatchTouchEvent得到,然后不调用onInterceptTouchEvent,直接调用了onTouchEvent方法。

    相关文章

      网友评论

          本文标题:Android事件传递机制

          本文链接:https://www.haomeiwen.com/subject/wgxnkqtx.html