【中软国际】HarmonyOS自定义控件之速度检测VelocityDetector 原创 精华
一般在涉及到滚动的场景时,我们会用到速度检测。比如列表滑动时,我们需要拿到手指抬起时的瞬时速度,来做惯性滚动。又比如在滚动翻页时,我们要根据手指速度来判断是否翻到下一页还是继续保持当页。
接下来我们就来看看HarmonyOS中的VelocityDetector如何使用。
使用方法
VelocityDetector使用起来还是比较简单的,主要是分为以下几步:
- 获取VelocityDetector实例
- 为VelocityDetector添加TouchEvent
- 计算速度
- 获取计算后的速度
- 清除已添加的event
获取实例
通过obtainInstance函数获取实例:
添加TouchEvent
在控件的TouchEventListener内调用addEvent函数:
计算速度
一般情况下,我们需要在手指抬起时计算速度,因为我们需要的是手指抬起后的速度值。因此我们可以在TouchEvent.PRIMARY_POINT_UP时调用calculateCurrentVelocity函数来计算速度:
calculateCurrentVelocity函数有两个重载:
其中:
- units为单位,1代表像素/毫秒,1000代表像素/秒,以此类推。一般情况下我们都传1000,获取的速度代表手指每秒移动多少像素
- maxVxVelocity为横向最大速度为多少,比如惯性滚动时,如果我们不希望滚动过快,可以设置一个最大速度
- maxVyVelocity为纵向最大速度为多少,比如惯性滚动时,如果我们不希望滚动过快,可以设置一个最大速度
获取速度
在计算速度之后就能直接获取速度值了:
获取到的速度可能是正值也可能是负值,正负值代表了速度的方向,这个大家可以通过日志自行实验一下。
清除
最后,我们需要清除前面添加的TouchEvent,为新一轮的事件做准备,避免旧的TouchEvent影响了后续的速度计算。这里我们在获取到速度后或者CANCEL事件中,就可以调用clear函数:
总结
VelocityDetector目前只能获取一个手指的速度,在多点触控的情况下,暂时没法获取其他手指的速度。
到此我们就获取到了手指抬起时的速度了,至于怎么利用这个速度,后续会在惯性滚动相关的文章中讲述。接下来我们再来分析一下VelocityDetector存在什么问题。
问题
首先我们来了解一下VelocityDetector的基本原理:
我们通过addEvent将TouchEvent传递给VelocityDetector,然后通过calculateCurrentVelocity来计算速度,在这个过程中,VelocityDetector基本上就是通过TouchEvent拿到手指的坐标,然后通过移动距离以及时间来计算速度。当然内部算法远比说的复杂,但是我们只需要记住一个关键变量即可:移动距离。
TouchEvent有两个函数可以拿到手指坐标来计算距离:getPointerPosition与getPointerScreenPosition。VelocityDetector究竟用的哪一个呢?我们可以通过如下代码来实验:
在手指移动过程中,日志如下:
答案很明显,VelocityDetector使用的是getPointerPosition。getPointerPosition获取的坐标是相对于父控件的,而不是屏幕的左上角,那么根据getPointerPosition的描述我们有理由猜测:
当被监听的控件,在手指移动过程中,不断的改变自己的位置,那么通过getPointerPosition获取的手指坐标会加上控件的位移量,导致滑动距离计算偏离预期。
下面我们来实验一下。在父布局中,子控件监听触摸事件,通过getPointerPosition获取手指坐标并计算MOVE与DOWN中坐标的差,并使用setComponentPosition与坐标差改变子控件的位置。
然后我们打印getPointerPosition获取的y坐标,getPointerScreenPosition获取的y坐标,以及移动距离,代码如下:
日志如下:
可以发现通过getPointerPosition计算出来deltaY是忽大忽小而不是线性增加的,并且与getPointerScreenPosition计算的deltaScreenY对比可以发现,deltaY等于deltaScreenY减去上一次的deltaY。也就证明了:通过getPointerPosition获取的手指坐标会加上该控件的位移量。
那么这对VelocityDetector有什么影响呢?VelocityDetector计算速度有一个重要的因素就是距离,在这种情况下距离忽大忽小,就会导致速度计算出来的值会小于正常速度,甚至于正负值都不太一样。
总结一下:当一个控件在该控件的触摸事件内,改变了自己相对于父控件的位置,那么通过VelocityDetector获取的速度就会出现误差。能影响控件位置的函数有setTop(在实验中setTop未能改变控件的位置,还不确定是为什么)、setContentPosition、setComponentPosition,甚至还包括setTranslationY、setTranslationX。并且如果在该控件的触摸事件内,父控件改变了位置,也会产生此问题。
在这种情况下,触摸事件内计算距离的问题好解决,不使用getPointerPosition直接使用getPointerScreenPosition即可。但是VelocityDetector的问题如何解决呢?两个办法:代理法与偏移法。
代理法
通过一个TouchEventProxy,内部维护一个TouchEvent,并将其getPointerPosition实现转发至TouchEvent的getPointerScreenPosition中。
使用起来也很简单:
位移法
通过反射TouchEvent发现,其内部含有能设置偏移量的函数,该函数会影响getPointerPosition的值。那么我们就可以在触摸事件内,对比getPointerPosition与getPointerScreenPosition的差,并通过函数设置偏移,强制使坐标同步。这里只提供位移法的可行并验证过的思路,代码大家可以自行尝试。
对比
既然有方法可以修复速度的问题,那么我们就可以对比修复前与修复后的速度,到底有多少差距。我们定义两个VelocityDetector实例,一个add代理,一个add原始的event,然后同时获取速度来看看:
快速上滑:
慢一点上滑:
先慢速最后快速上滑:
快速下滑:
慢速下滑:
先慢速最后快速下滑:
总结
可以发现上滑过程采样越少(慢速突然变快的情况)两个速度越接近,但是在下滑过程中,如果速度比较慢甚至会得到一个方向相反的速度。
作者:朱帆
更多原创内容请关注:中软国际 HarmonyOS 技术学院
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
这已经很全面了,前排插个眼