博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android进阶系列之事件分发详解
阅读量:4165 次
发布时间:2019-05-26

本文共 9824 字,大约阅读时间需要 32 分钟。

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

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

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

言归正传,事件分发是必备技能,因为在很多场景下,都会遇到一些事件冲突,虽然有各式各样的解决方法,但万变不离其宗,所以了解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_DOWNI/MyRelativeLayout: dispatchTouchEvent ACTION_DOWNI/MyRelativeLayout: onInterceptTouchEvent ACTION_DOWNI/MyTextView: dispatchTouchEvent ACTION_DOWNI/MainActivity: onTouch ACTION_DOWNI/MyTextView: onTouchEvent ACTION_DOWN

ACTION_UP的事件分发如下:

I/MainActivity: dispatchTouchEvent ACTION_UPI/MyRelativeLayout: dispatchTouchEvent ACTION_UPI/MyRelativeLayout: onInterceptTouchEvent ACTION_UPI/MyTextView: dispatchTouchEvent ACTION_UPI/MainActivity: onTouch ACTION_UPI/MyTextView: onTouchEvent ACTION_UPI/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也不是必须的。

你可能感兴趣的文章
《计算机网络 自顶向下方法》(第7版)答案(第三章)(二)
查看>>
《计算机网络 自顶向下方法》(第7版)答案(第三章)(三)
查看>>
《计算机网络 自顶向下方法》(第7版)答案(第四章)
查看>>
《计算机网络 自顶向下方法》(第7版)答案(第五章)
查看>>
编译原理语法分析“向前看”辨析
查看>>
HTTPSConnectionPool(host='files.pythonhosted.org', port=443): Read timed out.
查看>>
ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 拒绝访问。
查看>>
《计算机网络 自顶向下方法》(第7版)答案(第六章)(二)
查看>>
python 安装 cv2
查看>>
pip安装时错误:Bad md5 hash for package
查看>>
《计算机网络 自顶向下方法》(第7版)答案(第九章)
查看>>
tkinter 获取输入框的值AttributeError: 'NoneType' object has no attribute 'get'解决办法
查看>>
《计算机网络 自顶向下方法》(第7版)答案(第八章)(一)
查看>>
《计算机网络 自顶向下方法》(第7版)答案(第八章)(二)
查看>>
《计算机网络 自顶向下方法》(第7版)答案(第八章)(四)
查看>>
《计算机网络 自顶向下方法》(第7版)答案(第八章)(五)
查看>>
(测试)发言一
查看>>
(测试)发言三
查看>>
(测试)发言四
查看>>
(测试)发言五
查看>>