浅谈Android中的事件分发

本文介绍了Android中的事件分发机制,包括事件的基本概念、事件种类、事件传递的对象,详细阐述了事件分发过程中的dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法,以及在实际项目中的应用案例,帮助开发者理解如何处理和拦截触摸事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在我们实际的项目中,涉及到手势的操作很多,这就需要我们对Android的事件有一定的了解,才能设计出好的手势。本文就对Android中的事件分发做一些简单的介绍。

1、Android中事件的基本概念

1.1、事件分发的对象

事件分发的对象就是事件,当我们触摸屏幕的时候就会触发一系列的事件。
Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象。

1.2、事件的种类

Android中的事件分为4类:

  • MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
  • MotionEvent.ACTION_MOVE:滑动View
  • MotionEvent.ACTION_CANCEL:非人为原因结束本次事件(比如滑出手机屏幕外)
  • MotionEvent.ACTION_UP:抬起View(与DOWN对应)
    每次使用的时候都会组合触发这些事件,以down事件开始,以up或者cancel事件结束,如下图所示:
    在这里插入图片描述

1.3、事件传递的对象

在Android中,事件是在Activty、ViewGroup、View之间相互传递的。
当我们点击屏幕,MotionEvent在这些对象之间传递,就叫做事件分发。

2、事件分发的过程

2.1、相关方法

在完整分析事件分发过程之前,先来看下事件分发的相关方法

  • dispatchTouchEvent():Activity、ViewGroup、View中都存在该方法,是用来分发事件的,当触摸屏幕,就会触发Activity的此方法
  • onInterceptTouchEvent():只存在于ViewGroup,是用来拦截事件的,在ViewGroup的dispatchTouchEvent()内部触发
  • onTouchEvent(): Activity、ViewGroup、View中都存在该方法,是用来处理事件的,在dispatchTouchEvent内部触发

这些方法都会返回一个boolean变量,我们来对不同传递对象的各方法进行一个归纳总结:

Activity

dispatchTouchEvent()

  • super:调用ViewGroup的dispatchTouchEvent()方法
  • true/false:消费事件,结束当前事件的传递

onTouchEvent()

  • 不论返回什么,都会结束
ViewGroup

dispatchTouchEvent()

  • super:传递到当前ViewGroup的onInterceptTouchEvent()中
  • false:不消费,事件向上传递,传递到Activity的onTouchEvent()
  • true:消费事件,结束

onInterceptTouchEvent()

  • super/false:不拦截事件,事件向下传递,传递到子View的dispatchTouchEvent()
  • true:拦截事件,传递到当前ViewGroup的onTouchEvent()中

onTouchEvent()

  • super/false:不消费事件,事件向上传递,传递到Activity的onTouchEvent()中,如果返回false,当前ViewGroup不再接收该事件队列的后续事件
  • true:消费事件,结束
View

dispatchTouchEvent()

  • super:传递到当前View的onTouchEvent()中
  • false:不消费,事件向上传递,传递到ViewGroup的onTouchEvent()
  • true:消费事件,结束

onTouchEvent

  • super:这个针对不同的View,情况不太一样,如果当前View是clickable的(如Button),则消费事件,否则向上传递到ViewGroup的onTouchEvent()中
  • false:不消费事件,向上传递到ViewGroup的onTouchEvent()中,该View不再接收该事件队列的后续事件
  • true:消费事件,结束

具体看下表:
在这里插入图片描述

2.2、具体流程

在这里插入图片描述
注意看箭头的方向,默认实现并不一定是单纯的true或者false,是属于第三种情况,会调用到别的传递对象里的方法。
这里对上图作各个情况的梳理,见下面的流程图:
在这里插入图片描述

3、源码分析

这里简单做点源码分析,如果感兴趣的话,读者可以自行利用工具查看源码
一个事件首先触发的是Activity的dispatchTouchEvent()方法

public boolean dispatchTouchEvent(MotionEvent ev) {
        //关注点1
        //一般事件列开始都是DOWN,所以这里基本是true
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //关注点2
            onUserInteraction();
        }
        //关注点3
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

关注点1 一般事件列开始都是DOWN(按下按钮),所以这里返回true,执行onUserInteraction()
关注点2 该方法为空方法
从注释得知:当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
所以onUserInteraction()主要用于屏保

  /**
     * Called whenever a key, touch, or trackball event is dispatched to the
     * activity.  Implement this method if you wish to know that the user has
     * interacted with the device in some way while your activity is running.
     * This callback and {@link #onUserLeaveHint} are intended to help
     * activities manage status bar notifications intelligently; specifically,
     * for helping activities determine the proper time to cancel a notfication.
     *
     * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
     * be accompanied by calls to {@link #onUserInteraction}.  This
     * ensures that your activity will be told of relevant user activity such
     * as pulling down the notification pane and touching an item there.
     *
     * <p>Note that this callback will be invoked for the touch down action
     * that begins a touch gesture, but may not be invoked for the touch-moved
     * and touch-up actions that follow.
     *
     * @see #onUserLeaveHint()
     */
public void onUserInteraction() { 
}

关注点3 getWindow().superDispatchTouchEvent(ev)

Window类是抽象类

/**
     * Used by custom windows, such as Dialog, to pass the touch screen event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */
    public abstract boolean superDispatchTouchEvent(MotionEvent event);

PhoneWindow是Window类的唯一实现类,所以看一下PhoneWindow类中superDispatchTouchEvent()

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
  return mDecor.superDispatchTouchEvent(event);
//mDecor是DecorView的实例
//DecorView是视图的顶层view,继承自FrameLayout,是所有界面的父类
}

接下来我们看mDecor.superDispatchTouchEvent(event):

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
//DecorView继承自FrameLayout
//那么它的父类就是ViewGroupsuper.dispatchTouchEvent(event)方法,其实就应该是ViewGroup的dispatchTouchEvent()

}

所以就走到了ViewGroup的dispatchTouchEvent()中,这样事件就从Activity传递到了ViewGroup,后面源码就不分析了,感兴趣可以自己查看。

4、应用

简单应用场景,viewpager嵌套webview,而webview中含有可以左右滑动的选项卡,这样viewpager的左右滑动势必会和webview左右滑动的选项卡冲突。解决思路是,重写viewpager的onInterceptTouchEvent方法,在move事件时判断,当在界面的边缘滑动,并且是左右滑动时(x的滑动绝对值大于y的滑动绝对值),将滑动事件给父view,否则滑动事件还交给子view处理。实现代码如下:


public class EdgeSwitchPageViewPager extends ViewPager{
 
    private float startX;
    private float startY;
 
    public EdgeSwitchPageViewPager(Context context) {
        super(context);
    }
 
    public EdgeSwitchPageViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                startX = ev.getX();
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float nowX = ev.getX();
                float nowY = ev.getY();
                int screenWidth = DeviceUtils.getScreenWidth(getContext());
                //当按下时距离左边框或者右边框小于100px,且横向滑动幅度大于纵向滑动幅度时,将滑动焦点还给viewpager用于整个界面的切换
                //当在内部滑动时将滑动焦点给子类,用于子类滑动操作
                if((startX<100 && Math.abs(nowX - startX)>Math.abs(nowY - startY))
                        || ((screenWidth - startX) <100 && Math.abs(nowX - startX)>Math.abs(nowY - startY))){
                    return true;
                }else{
                    return false;
                }
 
            case MotionEvent.ACTION_UP:
 
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(ev);
    }
}

对于上面举例中的getX()做个简单的图解说明
在这里插入图片描述在这里插入图片描述
类似的用法还有很多,设计一些简单手势(比如长按,右滑显示页面,当然对于手势的设计用GestureDetector更好),拦截down事件的后续事件啊,拦截click事件啊等等,相信读者看了本文已经知道怎么做了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值