Android进阶系列之事件分发详解

曾经有个朋友问过我,你到底是想挣钱还是搞技术?

这个问题很简单,但是我却沉思了。这就关于自己的目标了,到底是安安心心有个工作,有钱拿就行?还是继续深入搞技术,宁愿待在工资高的小公司还是愿意待在工资低的大公司?晚上躺在床上想了想,还是搞技术吧,技术好了自然就有钱了。好吧~最终还是脱离不了钱,毕竟只是个庸俗的人。想想那么多要学的就蓝瘦香菇~


================================我是华丽的分手线=================================

言归正传,事件分发是必备技能,因为在很多场景下,都会遇到一些事件冲突,虽然有各式各样的解决方法,但万变不离其宗,所以了解Android里的事件分发机制,有利于理解Android的那些事件冲突从何而来,应该在哪个点着手解决。

主要的针对控件是Activity,View,ViewGroup的事件分发,拦截和消费。事件分发机制相关的方法dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent

(1)dispatchTouchEvent:事件的分发,

super.dispatchTouchEvent(ev):允许响应事件向下层(子控件)传递。

true:在dispatchTouchEvent方法中消费该事件,不下层传递。

false:在Activity(最外层控件)中,事件将被消费,在子控件中返回false会将该事件向上层传递,在父控件的onTouchEvent中处理。

(2)onInterceptTouchEvent:事件拦截,

super.onInterceptTouchEvent(ev):允许响应事件向下层(子控件)传递。

true:对事件进行拦截,事件被分配到该控件的onTouchEvent进行处理。

false:同super。

(3)onTouchEvent:事件响应,

super.onInterceptTouchEvent(ev):对事件进行消费(可理解为放行)后可调用onClick。

true:对事件进行消费(可理解为销毁)后不会调用onClick。(很多资料都说true和super没区别,这是个误区!)

false:将该事件向上层传递,在父控件的onTouchEvent中处理。

这里需要指出的是:Activity,View只有dispatchTouchEvent,onTouchEvent。

ViewGroup有dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent。

这些都是事件分发的基础,这些网上多得是,只是有一些细节要亲自试验才知道正确与否。

Activity嵌套Relative内嵌TextView

先自定义一个TextView,重写dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent。

public class MyTextView extends TextView {
    private static final String TAG = "MyTextView";
    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    /**
     * 在TextView中,只有dispatchTouchEvent和onTouchEvent
     * dispatchTouchEvent返回值:
     * (1) super.dispatchTouchEvent(ev):允许该响应事件向下层(子控件)传递
     * (2)true:在dispatchTouchEvent方法中消费该事件,不向下层传递
     * (3)false:把该事件响应停止向下层传递,向上层(父控件)传递,父控件的onTouchEvent中进行处理
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG,"dispatchTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG,"dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG,"dispatchTouchEvent ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    /**
     * 在TextView中,只有dispatchTouchEvent和onTouchEvent
     * onTouchEvent返回值:
     * (1) super.dispatchTouchEvent(ev):在该方法中消费该事件
     * (2)true:在该方法中消费该事件
     * (3)false:向上层(父控件)传递,父控件的onTouchEvent中进行处理
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG,"onTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG,"onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG,"onTouchEvent ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }
}

自定义RelativeLayout

public class MyRelativeLayout extends RelativeLayout {
    private static final String TAG = "MyRelativeLayout";

    public MyRelativeLayout(Context context) {
        super(context);
    }

    public MyRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG,"dispatchTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG,"dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG,"dispatchTouchEvent ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG,"onInterceptTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG,"onInterceptTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG,"onInterceptTouchEvent ACTION_UP");
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG,"onTouchEvent ACTION_DOWN");
                return super.onTouchEvent(event);
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG,"onTouchEvent ACTION_MOVE");
                return super.onTouchEvent(event);
            case MotionEvent.ACTION_UP:
                Log.i(TAG,"onTouchEvent ACTION_UP");
                return super.onTouchEvent(event);
            default:
                return super.onTouchEvent(event);
        }

    }
}

在Activity中

public class MainActivity extends AppCompatActivity implements View.OnTouchListener, View.OnClickListener {

    /**
     * 点击MyTextView会产生3个事件,分别是ACTION_DOWN,ACTION_MOVE,ACTION_UP,只有执行了ACTION_DOWN,ACTION_UP之后,并且onTouchEvent返回super才会执行onClick
     * 每个事件的正常默认传递流程是:
     * Activity dispatchTouchEvent --> MyTextView dispatchTouchEvent -->MyTextView onTouch -->MyTextView onTouchEvent ==>MyTextView onClick
     */
    private static final String TAG = "MainActivity";
    private MyTextView myTextView;
    private MyRelativeLayout myRelativeLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myTextView = (MyTextView) findViewById(R.id.my_textview);
        myRelativeLayout = (MyRelativeLayout) findViewById(R.id.activity_main);
        myTextView.setOnTouchListener(this);
        myTextView.setOnClickListener(this);
        /**
         *在事件分发中,只有最底层控件的才会触发onTouch方法
         */
        myRelativeLayout.setOnTouchListener(this);
        myRelativeLayout.setOnClickListener(this);
    }

    /**
     * 在Activity中,只有dispatchTouchEvent和onTouchEvent
     * dispatchTouchEvent返回值:
     * (1) super.dispatchTouchEvent(ev):允许该响应事件向下层(子控件)传递
     * (2)true:在dispatchTouchEvent方法中消费该事件,不向下层传递
     * (3)false:在dispatchTouchEvent方法中消费该事件,不向下层传递
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG,"dispatchTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG,"dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG,"dispatchTouchEvent ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    /**
     * onTouchEvent返回值:
     * (1) super.dispatchTouchEvent(ev):在该方法中消费该事件
     * (2)true:在该方法中消费该事件
     * (3)false:在该方法中消费该事件
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG,"onTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG,"onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG,"onTouchEvent ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * TextView的onTouch方法返回值:
     * (1)true:在onTouch方法中消费该事件,不向下层传递
     * (2)false:向下层传递
     */
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG,"onTouch ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG,"onTouch ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG,"onTouch ACTION_UP");
                break;
        }
        return false;
    }

    /**
     * TextView的onClick方法
     */
    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.my_textview){
            Log.i(TAG,"MyTextView onClick");
        }else {
            Log.i(TAG,"MyRelativeLayout onClick");
        }
    }
}



正常一次点击事件,打印的日志

ACTION_DOWN的事件分发如下:(和ACTION_MOVE一样)

I/MainActivity: dispatchTouchEvent ACTION_DOWN
I/MyRelativeLayout: dispatchTouchEvent ACTION_DOWN
I/MyRelativeLayout: onInterceptTouchEvent ACTION_DOWN
I/MyTextView: dispatchTouchEvent ACTION_DOWN
I/MainActivity: onTouch ACTION_DOWN
I/MyTextView: onTouchEvent ACTION_DOWN

ACTION_UP的事件分发如下:

I/MainActivity: dispatchTouchEvent ACTION_UP
I/MyRelativeLayout: dispatchTouchEvent ACTION_UP
I/MyRelativeLayout: onInterceptTouchEvent ACTION_UP
I/MyTextView: dispatchTouchEvent ACTION_UP
I/MainActivity: onTouch ACTION_UP
I/MyTextView: onTouchEvent ACTION_UP
I/MainActivity: MyTextView onClick

相应的事件分发流程图

总结:(1) 触摸事件的传递流程是从dispatchTouchEvent开始的,若不经过人为干扰,直接返回super会一层一层的向子控件传递,最后在子控件的onTouchEvent中进行处理,否则将一层一层向外传递,直至消失。

    (2)在向内层传递过程中,返回true事件就会提前消费。不会再向内层传递。

    (3)控件调用onTouchEvent方法之前,会调用onTouch方法,返回true表示在onTouch消费事件,返回false表示继续向下传递。


那么我想问一下,onClick是在view调用了ACTION_DOWN,ACTION_MOVE,ACTION_UP事件分发之后才触发,那么如果我在VIew的dispatchTouchEvent方法ACTION_DOWN时返回true,在ACTION_UP时返回super,会触发onClick方法吗?(希望读者自己动手实验一下,这些都是小细节)。

关于调用onClick方法,我试验了一些情况。给出以下总结,不对的欢迎指正。

TextView需要调用onClick,那么在ACTION_DOWN和ACTION_UP两个事件分发中,事件需要在TextView的onTouchEvent中消费,并且最后要返回super,返回true时无法调用onClick的。不信的可以试试。ACTION_MOVE也不是必须的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值