自学内容网 自学内容网

Android View事件分发

目录

1.什么是View事件分发?

2.事件的类型

3.事件的发生

4.事件分发的方法

4.1 dispatchTouchEvent()

4.2 onTouchEvent()

4.3 onInterceptTouchEvent()

5.滑动冲突

5.1 外部拦截法

5.2内部拦截法

6.onTouch的执行高于onClick

7. onTouch()和onTouchEvent()的区别

8.实现View的滑动

9.SurfaceView和View


个人对于Android事件分发的知识点总结,相对零散,源码了解的不够多,就没有展开说明。主要总结关于Android中的

View事件是如何分发的?如何传递的?如何消耗的?

1.什么是View事件分发?

将点击事件(MotionEvent)传递到某个具体的View或者ViewGroup处理的过程。事件分发是向下传递的,也就是父到子的顺序,当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象。

2.事件的类型

这里的事件指的就是点击事件(MotionEvent),主要分为以下几个类型:

(1)ACTION_DOWN:手指刚碰到屏幕的时候;

(2)ACTION_UP:手指离开屏幕的时候;

(3)ACTION_CANCEL:结束事件的时候(子View处理事件的过程中,父View拦截事件,收回处理权,此时的子View会收到这个事件);

(4)ACTION_MOVE:手指在屏幕上进行滑动的时候;

3.事件的发生

  • 在一次屏幕触摸事件中,ACTION_DOWN和ACTION_UP者两个事件是必须的,而ACTION_MOVE视情况而定,如果用户仅是点击了一下屏幕,那么可能只会监测到按下和抬起的动作。
  • 通过MotionEvent可以获得事件发生的x和y坐标,getX和getY返回的是相当于当前View左上角的X和Y坐标,getRawX和getRawY返回的是相当于手机屏幕左上角的X和Y坐标
  • 事件分发的本质就收将点击事件(MotionEvent)向某个View进行传递并最终得到处理,即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。
  • Android事件分发机制的本质是要解决,点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。
  • 事件分发流程:Native层 --> ViewRootImpl层 --> DecorView层 --> Activity层 --> ViewGroup层 --> View层
  • 当一个点击事件发生时,调用顺序如下
    • 1.事件最先传到Activity的dispatchTouchEvent()进行事件分发
    • 2.调用Window类实现类PhoneWindow的superDispatchTouchEvent()
    • 3.调用DecorView的superDispatchTouchEvent()
    • 4.最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的dispatchTouchEvent(),再由ViewGroup传递到View
    • 5.在ViewGroup中通过onInterceptTouchEvent()对事件传递是否进行拦截

4.事件分发的方法

Android 的事件分发机制主要涉及 ActivityViewGroupView 三者对触摸事件的传递和处理流程。该机制通过三个主要方法进行分发和拦截:

(1)dispatchTouchEvent():分发点击事件;

(2)onTouchEvent():处理点击事件;

(3)onInterceptTouchEvent():拦截某个事件;

4.1 dispatchTouchEvent()

作用对象:Activity,ViewGroup和View 里面的源码有兴趣可以去看看

它会返回一个布尔值。

  • 返回true
    • 消费事件
    • 事件不会往下传递
    • 后续事件(Move、Up)会继续分发到该View
  • 返回false
    • 不消费事件
    • 事件往下传递
    • 将事件回传给父控件的onTouchEvent()处理
      • Activity例外:返回false=消费事件
    • 后续事件(Move、Up)会继续分发到该View(与onTouchEvent()区别)
4.2 onTouchEvent()

作用对象:Activity,ViewGroup和View

作用:在dispatchTouchEvent()内部调用,用于处理点击事件。它会返回一个布尔值。

  • 返回true
    • 自己处理(消费)该事情
    • 事件停止传递
    • 该事件序列的后续事件(Move、Up)让其处理;
  • 返回false(同默认实现:调用父类onTouchEvent())
    • 不处理(消费)该事件
    • 事件往上传递给父控件的onTouchEvent()处理
    • 当前View不再接受此事件列的其他事件(Move、Up);
4.3 onInterceptTouchEvent()

该方法表示是否对事件进行拦截,只有在ViewGroup里才有此方法,代表这个事件是否往下传递

  • true-当前ViewGroup希望该事件不再传递给其child,而是希望自己处理。
  • false-当前ViewGroup不准备拦截该事件,事件正常向下分发给其child。

当用户进行触摸/点击事件后,他们的执行顺序

dispatchTouchEvent()--->onInterceptTouchEvent()--->onTouchEvent()

一般来说事件是这样传递的:

  • 从Activity A---->ViewGroup B--->View C,从上往下调用dispatchTouchEvent()
  • 再由View C--->ViewGroup B --->Activity A,从下往上调用onTouchEvent()

5.滑动冲突

当父容器与子 View 都可以滑动时,就会产生滑动冲突。解决 View 之间的滑动冲突的方法分为两种,分别是外部拦截法内部拦截法

5.1 外部拦截法

即在父容器进行选择性拦截操作,即使用onInterceptTouchEvent方法。

例如对指定方向的滑动进行拦截,当满足条件时,返回true,表示此事件被拦截,不进行事件下发

tips:1.ACTION_DOWN 事件需要返回 false,父容器不能进行拦截,否则根据 View 的事件分发机制,后续的 ACTION_MOVE 与 ACTION_UP 事件都将默认交由父容器进行处理。

2.原则上 ACTION_UP 事件也需要返回 false,如果返回 true,那么子 View 将接收不到 ACTION_UP 事件,子 View 的onClick 事件也无法触发。

例子:实现一个自定义ScrollView

class CustomScrollView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : ScrollView(context, attrs) {

    private var lastX = 0f
    private var lastY = 0f

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                lastX = ev.x
                lastY = ev.y
                // 不拦截,让子视图接收事件
                return false
            }
            MotionEvent.ACTION_MOVE -> {
                val deltaX = Math.abs(ev.x - lastX)
                val deltaY = Math.abs(ev.y - lastY)

                // 如果 Y 轴移动距离大于 X 轴,则父容器拦截
                return deltaY > deltaX
            }
        }
        return super.onInterceptTouchEvent(ev)
    }
}

在此实现中,onInterceptTouchEvent 方法会根据手指滑动的方向来决定是否拦截事件。如果纵向滑动距离更大,则 ScrollView 会拦截事件。

5.2内部拦截法

内部拦截法在子视图(如 RecyclerView)中处理,通过对子视图的 onTouchEvent 进行控制,让子视图自己决定是否请求父容器不要拦截。

例子:自定义一个RecycleView

class CustomRecyclerView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : RecyclerView(context, attrs) {

    private var lastX = 0f
    private var lastY = 0f

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                lastX = ev.x
                lastY = ev.y
                // 请求父容器不要拦截事件
                parent.requestDisallowInterceptTouchEvent(true)
            }
            MotionEvent.ACTION_MOVE -> {
                val deltaX = Math.abs(ev.x - lastX)
                val deltaY = Math.abs(ev.y - lastY)

                // 当检测到横向滑动时,允许父容器拦截
                if (deltaX > deltaY) {
                    parent.requestDisallowInterceptTouchEvent(false)
                } else {
                    parent.requestDisallowInterceptTouchEvent(true)
                }
            }
        }
        return super.onTouchEvent(ev)
    }
}

在这个示例中,RecyclerView 会在接收到横向滑动时调用 parent.requestDisallowInterceptTouchEvent(false),使父容器可以拦截事件;而在纵向滑动时调用 parent.requestDisallowInterceptTouchEvent(true),让父容器不拦截。

总结

  • 外部拦截法适合在父容器中根据滑动方向决定是否拦截事件。
  • 内部拦截法让子视图根据滑动方向来请求父容器是否拦截事件。

6.onTouch的执行高于onClick

因为每当控件被点击时:

  • 如果在回调onTouch()里返回false,就会让dispatchTouchEvent方法返回false,那么就会执行onTouchEvent();如果回调了setOnClickListener()来给控件注册点击事件的话,最后会在performClick()方法里回调onClick()。
    • onTouch()返回false(该事件没被onTouch()消费掉) = 执行onTouchEvent() = 执行OnClick()
  • 如果在回调onTouch()里返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行;
    • onTouch()返回true(该事件被onTouch()消费掉) = dispatchTouchEvent()返回true(不会再继续向下传递) = 不会执行onTouchEvent() = 不会执行OnClick()
  • onTouch->onTouchEvent->onClick

7. onTouch()和onTouchEvent()的区别

  • 这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。
  • 如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。

8.实现View的滑动

  • layout:对View进行重新布局定位。在onTouchEvent()方法中获得控件滑动前后的偏移。然后通过layout方法重新设置。
  • offsetLeftAndRight和offsetTopAndBottom:系统提供上下/左右同时偏移的API。onTouchEvent()中调用
  • LayoutParams: 更改自身布局参数(设置margin或者父容器的padding等等)
  • scrollTo/scrollBy: 本质是移动View的内容,需要通过父容器的该方法来滑动当前View
  • Scroller: 平滑滑动,通过重载computeScroll(),使用scrollTo/scrollBy完成滑动效果。
  • 属性动画: 动画对View进行滑动

9.SurfaceView和View

SurfaceView是从View基类中派生出来的显示类,他和View的区别有:

  • View需要在UI线程对画面进行刷新,而SurfaceView可在子线程进行页面的刷新
  • View适用于主动更新的情况,而SurfaceView适用于被动更新,如频繁刷新,这是因为如果使用View频繁刷新会阻塞主线程,导致界面卡顿
  • SurfaceView在底层已实现双缓冲机制,而View没有,因此SurfaceView更适用于需要频繁刷新、刷新时数据处理量很大的页面


原文地址:https://blog.csdn.net/LoveFHM/article/details/139481107

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!