重撸Android事件分发

来源:互联网 发布:yii框架 商城源码 编辑:程序博客网 时间:2024/06/05 02:27

概述

Android事件分发从我刚开始开发以来就已经撸过好几遍,但感觉一直没有完全理解透彻,今天这篇文章从作者自身出发,希望给读者带来一个最简单明了的理解流程,也算是给自己一边巩固的机会

正文

在安卓事件分发系统中有3个非常重要的概念:

  1. 分发(dispatch)
  2. 拦截(intercept)
  3. 消费(consume)

这三个概念也就对应着3个非常重要的方法,从它们的==名字==就可以看出来:

  1. 分发:dispatchTouchEvent(MotionEvent ev)
  2. 拦截:onInterceptTouchEvent(MotionEvent ev)
  3. 消费:onTouchEvent(MotionEvent ev)

这3个方法在Activity、ViewGroup、View中表现略有不同,ViewGroup中多了onInterceptTouchEvent方法

  1. Activity : dispatchTouchEvent、onTouchEvent
  2. ViewGroup : dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent
  3. View : dispatchTouchEvent、onTouchEvent

我们看到这3个方法中的参数都为MotionEvent ,这是一个运动事件的类,就像一个普通的点击事件或者滑动事件,都是运动事件。运动事件的类型很多,我们这里只关心最常用的三种:

  1. ACTION_DOWN :点击屏幕的事件
  2. ACTION_MOVE : 在屏幕上移动的事件
  3. ACTION_UP : 离开屏幕的事件

在了解了以上信息之后,那么这3个事件分发的方法到底有什么关系呢?

我们直接上代码,首先为了模拟Activity、ViewGroup、View三种情况,我们自定义一个ChildView继承TextView :

public class ChildView extends AppCompatTextView {    public ChildView(Context context) {        super(context);    }    public ChildView(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        Log.w("touchDemo", "ChildView | onTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));        return super.onTouchEvent(ev);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.w("touchDemo", "ChildView | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));        return super.dispatchTouchEvent(ev);    }}

然后自定义一个FatherViewGroup继承LinearLayout :

public class FatherViewGroup extends LinearLayout {    public FatherViewGroup(Context context) {        super(context);    }    public FatherViewGroup(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.w("touchDemo", "FatherViewGroup | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));        return super.dispatchTouchEvent(ev);    }    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.w("touchDemo", "FatherViewGroup | onInterceptTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));        return super.onInterceptTouchEvent(ev);    }    public boolean onTouchEvent(MotionEvent ev) {        Log.w("touchDemo", "FatherViewGroup | onTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));        return super.onTouchEvent(ev);    }}

Activity就用我们的MainActivity就好了:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        findViewById(R.id.child).setOnClickListener(                new View.OnClickListener() {                    @Override                    public void onClick(View v) {                        Log.w("touchDemo", "ChildView | onClick -------");                    }                }        );        findViewById(R.id.child).setOnTouchListener(                new View.OnTouchListener() {                    @Override                    public boolean onTouch(View v, MotionEvent event) {                        Log.w("touchDemo", "ChildView | onTouch --> " + TouchEventUtil.getTouchAction(event.getAction()));                        return false;                    }                }        );    }    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.w("touchDemo", "MainActivity | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));        return super.dispatchTouchEvent(ev);    }    public boolean onTouchEvent(MotionEvent event) {        Log.w("touchDemo", "MainActivity | onTouchEvent --> " + TouchEventUtil.getTouchAction(event.getAction()));        return super.onTouchEvent(event);    }}

在Activity中,我们给ChildView加一个OnClickListener监听和一个OnTouch监听,这样是为了查看他们的顺序,在一些公司的面试题中会有这方面的提问。

经过N多次不断的变换方法的返回值,我们可以得到如下一系列结论:

  • 事件的传递顺序是:Activity–>ViewGroup–>View,也就是说事件传递总是从外层到内层,从父视图到子视图
  • 对于View来说,事件的传递顺序是:dispatchTouchEvent–>onTouch–>onTouchEvent–>onClick,可怜的onClick的优先级最低
  • 事件的传递顺序在3个方法中的体现为:dispatchTouchEvent–>onInterceptTouchEvent(如果为View或者Activity则没有这一步)–>onTouchEvent
  • View默认会消耗事件,也就是说onTouchEvent默认返回的是true
  • ViewGroup默认不会拦截事件,也就是说onInterceptTouchEvent默认返回是false
  • 对于View来说,如果一旦消费了ACTION_DOWN事件,那么后续的ACTION_MOVE,ACTION_UP事件都会直接交由这个View来处理;同样情况如果是ViewGroup的话,要通过onInterceptTouchEvent拦截下来再消费,并且,一旦消费了,后续的时间就不会再询问onInterceptTouchEvent是否拦截了。
  • 对于View或者ViewGroup来说,如果不消费ACTION_DOWN事件,那么后续事件也不会交由其处理了,换句话说,你领导教给你个任务,如果你搞砸了,后续一段时间估计不会安排任务给你做了。
  • 事件总是从外层到内存传递,但是子视图可以通过getParent().requestDisallowInterceptTouchEvent(true)来干预父视图的事件分发

对于这些结论,我一点也不建议大家靠背来记住他们,我建议自己新建一个工程,通过日志来验证上述结论来加深自己的理解和印象。

滑动冲突

讲了事件分发,就不得不讲滑动冲突了,我们在日常开发中经常会遇到滑动冲突,常见的像ViewPager中的Fragment包含了横向的广告条RecycleView或者ScrollerView就会造成滑动冲突。

常见的滑动冲突一般分为三种:

  1. 方向相同的
    这里写图片描述
  2. 方向不同的
    这里写图片描述
  3. 情况1和情况2混合的,这种情况比较复杂,但是处理的方法也是情况1和情况2结合起来,所以我们的重点放在前两种情况上

滑动冲突的处理规则:

  1. 对于情况1来说,我们需要通过业务需求来确定,改方向的滑动到底要作用于哪个View,然后再作出对应的处理

  2. 对于情况2,我们首先要判断到底是纵向直的滑动还是横向的滑动,再根据业务需求来确定滑动要作用的目标View。判断滑动的方向一般是通过横向滑动与纵向滑动距离只差来判别,如果横向>纵向,则判断为横向滑动,反之亦然。

滑动冲突的处理方法:

  • 外部拦截:简单来说就是,事件都由外部父容器先处理,由父容器来决定先拦截或消费哪些事件,不需要拦截消费的就分发给子View,对应的代码为:
public boolean onInterceptTouchEvent(MotionEvent ev) {    boolean result = false;    switch (ev.getAction()) {        case MotionEvent.ACTION_DOWN:            result = false;            break;        case MotionEvent.ACTION_MOVE:            if (父容器是否要处理) {                result = true;            } else {                result = false;            }            break;        case MotionEvent.ACTION_UP:            result = false;            break;        default:            break;    }    return result;}
  • 内部处理:所有的事件都分发到子视图中,如果子视图需要消费就由子视图来消费,如果子视图不要则交由父容器处理。对应代码:
public boolean dispatchTouchEvent(MotionEvent ev) {    switch (ev.getAction()) {        case MotionEvent.ACTION_DOWN:            getParent().requestDisallowInterceptTouchEvent(true);            break;        case MotionEvent.ACTION_MOVE:            if (父容器是否要处理) {                getParent().requestDisallowInterceptTouchEvent(false);            }            break;        case MotionEvent.ACTION_UP:            break;        default:    }    return super.dispatchTouchEvent(ev);}

总结

对于Android的事件分发就总结到这了,加深记忆最好的方法是多多练习,多多尝试,理解滑动冲突最好的办法就是在实际开发中遇到了然后自己亲手解决,希望这篇文章能给大家带来帮助。

原创粉丝点击