Android事件传递机制

2018-12-23  本文已影响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方法。

上一篇下一篇

猜你喜欢

热点阅读