欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

站在初学者的角度看图理解Android事件分发机制流程

程序员文章站 2022-05-13 09:52:01
关于Android事件分发机制的文章已经烂大街了,但很多都并不是站在初学者角度分析,要想快速了解Android事件分发机制,我们应该从以下这几方面先开始了解。1.什么是事件分发?这里谈下我个人的通俗理解: 就是我们的Activity、ViewGroup、View 都重写了 onTouchEvent(MotionEvent event) 这个方法后,用户触摸屏幕后它们的onTouchEvent是否能够接收到event事件的过程2. 什么时候会用到事件分发?当发生了点击事件或......

 

关于Android事件分发机制的文章已经烂大街了,但很多都并不是站在初学者角度分析,要想快速了解Android事件分发机制,我们应该从以下这几方面先开始了解。

 

1.什么是事件分发?

这里谈下我个人的通俗理解:  就是我们的Activity、ViewGroup、View 都重写了 onTouchEvent(MotionEvent event) 这个方法后,用户触摸屏幕后它们的onTouchEvent是否能够接收到event事件的过程

 

2. 什么时候会用到事件分发?

当发生了点击事件或滑动事件冲突时,例如scrollView嵌套后子scrollView无法滑动这样的情况,此时要使用到事件分发机制。通过自定义控件继承发生冲突控件并重写其一系列事件分发的方法,来决定是否拦截事件和分发到子View处理。

 

3.事件分发都会用到哪些方法?

 主要都是在View,ViewGroup 或 Activity中重写以下这三个方法,通过return值 决定事件分发的方向。

boolean  onTouchEvent(MotionEvent event)       //Activity ViewGroup View都可以重写此方法 

boolean  dispatchTouchEvent(MotionEvent event) //Activity ViewGroup View都可以重写此方法

boolean  onInterceptTouchEvent (MotionEvent event) //仅有 ViewGroup 可以重写此方法 

 事件源 :事件源就是MotionEvent ,一个 MotionEvent 包含手指 按下 (down) , 移动(move), 抬起(up) ,以及对应事件的坐标值。

 

一个事件源都是先通过AvtivitydispatchTouchEvent开始,依次往 子View 的 dispatchTouchEvent 传,最后一个子ViewdispatchTouchEvent 把事件交给其 onTouchEvent 进行消费处理,一个事件就结束。 若子View onTouchEvent 不消费这个事件则该事件又会一直往父View onTouchEvent 回传,直到回传到 Avtivity 的onTouchEvent 消费事件。

 

  • boolean dispatchTouchEvent(MotionEvent event)  代表是否继续往下分发 ,返回值意义:

        return true:事件不再往内部子View分发,并自己消费了这个事件。

        return false: 事件不再往内部子View分发,直接交给外部父View onTouchEvent 处理。 若当前已是最外层Activity则事件结束。

        return super.dispatchTouchEvent(event) :  事件继续往下分发。若当前为ViewGroup,则交给 onInterceptTouchEvent 决定是否处理; 否则事件交给子View的 dispatchTouchEvent方法继续决定,若已经是最底层的子View则将事件交给自己的 onTouchEvent 处理。

 

  • boolean onTouchEvent(MotionEvent event)   处理事件方法并决定是否把事件继续交给父View处理,其返回值意义:

       return true:自己触发了onTouchEvent , 并消费了这个事件 。

       return false: 自己触发了onTouchEvent , 不消费了这个事件,  事件继续往上回传触发父View 的 onTouchEvent 。

       return super.onTouchEvent(event) : 同 return false。

 

  • boolean  onInterceptTouchEvent (MotionEvent event) 是否拦截事件自己处理不再往下传递其返回值意义:

       注意子view 设置了getParent().requestDisallowInterceptTouchEvent(true)时,则不会被父View的onInterceptTouchEvent 拦截触发

       return true:拦截此次事件不再往下传 , 并交给自己的onTouchEvent触发 。

       return false: 不拦截此次事件继续再往下传给子View的dispatchTouchEvent 

       return super.onInterceptTouchEvent (event) : 同 return false。

 

4. 通过代码模拟事件分发练习,来理解事件分发流程图

下面是我自己画的一个事件流程图,这里加上了requestDisallowInterceptTouchEvent 这个决定父View是否允许拦截事件这个重要的方法。先对这个图有点印象,边敲代码边学会看流程图:

站在初学者的角度看图理解Android事件分发机制流程

 

5.  决定需要模拟的效果,开始代码练习

模拟假设我们有以下的这样一个场景,一个Activity , 一个ViewGroup, 一个View ,他们都是互相包含关系:

站在初学者的角度看图理解Android事件分发机制流程

 为了观察FrameLayout 和 TextView 的事件分发机制,我们需要自己写个继承它们2个的自定义View并重写其事件分发方法。

//自定义View
class MyTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {

    //重写View的事件分发
    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
        Log.d("TAG","[View] dispatchTouchEvent....${event.action.getActionDesc()}")
        return super.dispatchTouchEvent(event)
    }

    //重写View的事件处理
    override fun onTouchEvent(event: MotionEvent): Boolean {
        Log.d("TAG","[View] onTouchEvent....${event.action.getActionDesc()}")
        return super.onTouchEvent(event)
    }
}
//自定义ViewGroup
class MyFrameLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
    
    //重写ViewGroup的事件分发
    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
        Log.d("TAG","[ViewGroup] dispatchTouchEvent....${event.action.getActionDesc()}")
        return super.dispatchTouchEvent(event)
    }

    //重写ViewGroup的事件处理
    override fun onTouchEvent(event: MotionEvent): Boolean {
        Log.d("TAG","[ViewGroup] onTouchEvent....${event.action.getActionDesc()}")
        return super.onTouchEvent(event)
    }

    //ViewGroup特有的事件拦截方法
    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        Log.d("TAG","[ViewGroup] onInterceptTouchEvent....${event.action.getActionDesc()}")
        return super.onInterceptTouchEvent(event)
    }
    
}

以及MainActivity的事件处理方法

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    // 重写Activity事件分发方法
    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
        Log.d("TAG","[Activity] dispatchTouchEvent.... ${event.action.getActionDesc()}")
        return super.dispatchTouchEvent(event)
    }

    // 重写Activity事件处理方法
    override fun onTouchEvent(event: MotionEvent): Boolean {
        Log.d("TAG","[Activity] onTouchEvent....${event.action.getActionDesc()}")
        return super.onTouchEvent(event)
    }

}

上面的getActionDesc方法是自己定义的一个的扩展方法,因为event.action返回的是一个Int,所以定义一个方法根据Int值来方便显示当前是down up move描述。

fun Int.getActionDesc() = when (this) {
    0 -> "手指down事件"
    1 -> "手指up事件"
    2 -> "手指move事件"
    else -> ""
}

MainActivity的布局使用了我们自定义MyFrameLayout和MyTextView, 简单如下:

站在初学者的角度看图理解Android事件分发机制流程

 

首先我们的 Activity, ViewGroup,View 默认的所有事件分发方法返回值都是 super , 这里我们在TextView区域滑动一下, 可以看到以下Log日志:

[Activity]       dispatchTouchEvent.... 手指down事件
[ViewGroup] dispatchTouchEvent....手指down事件
[ViewGroup] onInterceptTouchEvent....手指down事件
[View]           dispatchTouchEvent....手指down事件
[View]           onTouchEvent....手指down事件
[ViewGroup] onTouchEvent....手指down事件
[Activity]       onTouchEvent....手指down事件

[Activity]       dispatchTouchEvent.... 手指move事件
[Activity]       onTouchEvent....手指move事件
[Activity]       dispatchTouchEvent.... 手指move事件
[Activity]       onTouchEvent....手指move事件
[Activity]       dispatchTouchEvent.... 手指move事件
[Activity]       onTouchEvent....手指move事件
[Activity]       dispatchTouchEvent.... 手指move事件
[Activity]       onTouchEvent....手指move事件
[Activity]       dispatchTouchEvent.... 手指up事件
[Activity]        onTouchEvent....手指up事件

可以看到,我们默认所有都返回super的时候,所有的down事件符合我们事件分发的流程图结果。 

而move和up事件默认被Activity自己处理了不往下分发。

 

再看当我们的MyTextView onTouchEvent返回 true 时,无论down,move, up 事件都是按照流程图分发

[Activity]       dispatchTouchEvent.... 手指down事件
[ViewGroup] dispatchTouchEvent....手指down事件
[ViewGroup] onInterceptTouchEvent....手指down事件
[View]           dispatchTouchEvent....手指down事件
[View]           onTouchEvent....手指down事件

[Activity]       dispatchTouchEvent.... 手指move事件
[ViewGroup] dispatchTouchEvent....手指move事件
[ViewGroup] onInterceptTouchEvent....手指move事件
[View]           dispatchTouchEvent....手指move事件

[View]           onTouchEvent....手指move事件
[Activity]       dispatchTouchEvent.... 手指move事件
[ViewGroup] dispatchTouchEvent....手指move事件
[ViewGroup] onInterceptTouchEvent....手指move事件
[View]           dispatchTouchEvent....手指move事件

......
[View]           onTouchEvent....手指move事件[Activity] dispatchTouchEvent.... 手指up事件
[ViewGroup] dispatchTouchEvent....手指up事件
[ViewGroup] onInterceptTouchEvent....手指up事件
[View]           dispatchTouchEvent....手指up事件
[View]            onTouchEvent....手指up事件

综上所述,我们可以对照着流程图,一个个试着从Activity,ViewGroup,View逐个尝试返回不同的值观察不同的事件传递结果。

结论:

事件分发的 down事件总会按照事件分发的流程图走,而之后的up , move事件的话则会根据第一次的down事件分发结果决定。

若down事件最后由 Activity onTouchEvent进行处理,则之后的up,move事件则不会往下分发直接由Activity的dispatchTouchEvent交给自己的onTouchEvent进行处理;否则down,up,move事件都会按照事件流程图走。

 

6. 最后,了解View的onTouchEvent 、setOnTouchListener、setOnClickListener之间的影响

 

onTouchEvent:View自身的一个方法,通过重写它来处理传递来的事件。

 

setOnTouchListener:功能和onTouchEvent一样,暴露给外部Activity对View的处理事件的方法,优先级比 onTouchEvent高。

当在Activity里对View设置了该方法并返回false时,先触发setOnTouchListener再触发onTouchEvent;返回true时View内部的onTouchEvent方法不会触发。

  textView.setOnTouchListener { view, event ->
      Log.d("TAG","[view.setTouchListener] onTouchListener....${event.action.getActionDesc()} ")
      true
  }

点击TextView后Log如下:

[Activity]                          dispatchTouchEvent.... 手指down事件
[ViewGroup]                    dispatchTouchEvent....手指down事件
[ViewGroup]                    onInterceptTouchEvent....手指down事件
[View]                               dispatchTouchEvent....手指down事件
[view.setTouchListener] onTouchListener....手指down事件 

[Activity]                            dispatchTouchEvent.... 手指up事件
[ViewGroup]                      dispatchTouchEvent....手指up事件
[ViewGroup]                      onInterceptTouchEvent....手指up事件
[View]                                dispatchTouchEvent....手指up事件
[view.setTouchListener]    onTouchListener....手指up事件 

 

setOnClickListener:我们常用的点击事件,当自身onTouchEvent down和up事件再短时间内完成则会触发。若onTouchEvent 或 setOnTouchListener 返回 true 消费了事件时则点击事件不会触发

若想若onTouchEvent 或 setOnTouchListener 返回 true 并继续触发其点击事件,则需调用自身的 performClick()来继续触发点击事件

tv.setOnTouchListener { view, event ->
     Log.d("TAG","[view.setTouchListener] onTouchListener....${event.action.getActionDesc()} ")
     view.performClick()
     true
}

 

 

本文地址:https://blog.csdn.net/zhong_zihao/article/details/107505027

相关标签: Android kotlin