【中软国际】HarmonyOS 自定义控件之触摸事件与事件分发 原创 精华
本篇文章所讨论的触摸事件与事件分发只局限于控件内,不涉及到更上层。
触摸事件
如何监听触摸事件
HarmonyOS中可以通过Listener的方式:
注意:setTouchEventListener会被覆盖
常用的触摸事件的类型
这里我们对比其他主流系统中MotionEvent与HarmonyOS中TouchEvent来方便理解与记忆。
MotionEvent的常用的事件类型与HarmonyOS中的TouchEvent类型基本可以对应起来:
- MotionEvent.ACTION_CANCEL -> TouchEvent.CANCEL
- MotionEvent.ACTION_HOVER_ENTER -> TouchEvent.HOVER_POINTER_ENTER
- MotionEvent.ACTION_HOVER_EXIT -> TouchEvent.HOVER_POINTER_EXIT
- MotionEvent.ACTION_HOVER_MOVE -> TouchEvent.HOVER_POINTER_MOVE
- MotionEvent.ACTION_POINTER_DOWN -> TouchEvent.OTHER_POINT_DOWN
- MotionEvent.ACTION_POINTER_UP -> TouchEvent.OTHER_POINT_UP
- MotionEvent.ACTION_MOVE -> TouchEvent.POINT_MOVE
- MotionEvent.ACTION_DOWN -> TouchEvent.PRIMARY_POINT_DOWN
- MotionEvent.ACTION_UP -> TouchEvent.PRIMARY_POINT_UP
常用的Api
- 获取事件类型
- 获取手指相对于屏幕的x、y坐标
- 获取手指相对于父控件的x、y坐标
- getPointerScreenPosition与getPointerPosition的区别
前者是相对的屏幕的坐标,而后者是相对于父控件的坐标。如果在手指滑动过程中,对该控件做了位移,那么getPointerPosition获取的坐标将会是手指本身坐标加上控件的位移量,导致位移异常。
这里建议,如果需要根据坐标来计算,都使用getPointerScreenPosition比较保险。
- 总结
TouchEvent提供了基础api,但是没有MotionEvent内一些比较高阶的api,比如obtain等。接下来我们来关注更为重要的事件分发。
事件分发
事件分发是一套比较重要同时也比较复杂的机制,如果不熟悉这套机制,那么在遇到稍微复杂的滑动失效问题就会觉得手足无措。在这里通过打印日志的方式来摸索HarmonyOS上的事件的传递机制。
HarmonyOS中事件的传递机制
首先,我们通过打印日志的方式,来摸索触摸事件是如何在Component中传递的。经过实验,发现如下几条规律:
- 事件首先会传递到最底层的目标控件,而非顶层的父控件
- 如果目标控件不处理该事件,即onTouchEvent返回false,那么事件冒泡到父控件
- 如果目标控件处理了该事件,即onTouchEvent返回true,那么后续事件不会向上冒泡,而是直接被目标控件消费
- 如果一个控件在down事件中,返回了false,那么后续的事件也不会被传递到该控件中
- 如果一个控件接受到了down事件,并返回了true,那么后续的事件会直接被传递到该控件中,其他控件不会收到事件
HarmonyOS中的事件传递更像是冒泡,而非分发,down事件一旦被某一个控件消费了,那么其他控件将都收不到后续事件了。这样的机制比较难去实现一些复杂的嵌套效果。
比如子控件响应横向滑动,父控件响应垂直滑动这种情况。子控件如果要想收到后续的move事件,只能在down的时候返回true,这样就导致父控件完全收不到触摸事件。子控件如果像要在move时判断滑动方向而down事件返回了false,那么子控件将再也接收不到后续的事件了。
HarmonyOS的事件冒泡比较简单,一旦约定好就再也没有反悔的机会了。那么如何类似其他主流系统一样,从顶层控件分发并且可以拦截事件呢?
这里只提供思路,具体代码可以参考:事件分发
实现事件分发
我们构想中的事件分发应该是这样:事件是首先到顶层的父控件,然后经过dispatchTouchEvent一层层向下分发。ComponentContainer可以通过onInterceptTouchEvent拦截事件,并交给自己的onTouchEvent来处理。如果ComponentContainer不处理事件则继续向下分发,直到最终的Component控件。这样的机制意味着每一层都有机会能拿到事件,那么如何在HarmonyOS中实现呢?
我们可以将事件分发相关的函数与代码,抽取出来,移植到HarmonyOS中,并通过一些手段应用到HarmonyOS的onTouchEvent中。
抽象
HarmonyOS中没有dispatchTouchEvent、onInterceptTouchEvent等函数,如何应用到组件中呢?抽象接口,将事件分发相关的函数抽象成两个接口:
View
ViewGroup
实现
然后借助两个帮助类,来实现两个接口中的相关函数。将View中事件分发的具体代码封装到ViewHelper中,将ViewGroup中事件分发的具体代码封装到ViewGroupHelper中。
分发
最后借助一个分发帮助类DispatchHelper,来将HarmonyOS中的事件,从顶层开始按照ViewGroupHelper中的dispatchTouchEvent来分发。
DispatchHelper主要做了下面几件事:
- 缓存当次事件中,视图树内所有实现了View、ViewGroup接口的控件
- 从最顶层的控件开始,调用其dispatchTouchEvent函数
- 过滤掉由于事件冒泡,而传递过来的可能的重复事件
代码:
使用方式
参考文档
注意事项
虽然能使用事件分发了,但是由于底层机制的不同,在使用上还是会有一些差别:
- 如果根布局或者中间的ComponentContainer实现的是View而非ViewGroup,那么事件将不会继续往下传递。
- 视图树中间可以出现断层,即出现未实现View或ViewGroup的控件,事件会跳过并往下传递。
- 未实现View或ViewGroup的控件,如果设置了setTouchEventListener,那么事件将在回调返回true后直接被消费,而导致不会被分发。
- 如果遇到super.onTouchEvent或者super.onInterceptTouchEvent,需要去父类查看逻辑并移植进来,如果是普通的布局或者控件一般是可以忽略,或者返回false的。
- 如果遇到super.dispatchTouchEvent则可以直接使用ViewGroupHelper/ViewHelper的dispatchTouchEvent来替代。
- 暂时只支持单点触摸的分发
作者:朱帆
很详细,注释也齐全hhh
帆写的真好
仰望大佬666
非常有参考价值,mark