App Inventor中画布组件的多点触控实现
2022-08-31杨道全
摘要:有经验的App Inventor 爱好者希望能够定制系统,增加自己需要的功能。文章以 App Inventor中的画布组件为例,分析安卓系统中触控事件的处理机制,详细介绍画布组件多点触控的实现方法,并实现了缩放手势和旋转手势控制功能,为有意定制 App Inventor的爱好者提供参考。
关键词:App Inventor;画布;多点触控;手势控制
中图分类号:TP319 文献标识码:A
文章编号:1009-3044(2022)17-0061-03
1 概述
App Inventor 系统简单易学,可视化的编程体验圈定了不少爱好者,他们经过一段时间的使用后发现,“简单”既是App Inventor 的优势也是其制约。系统封装后所提供的配置项很少,无法满足个性化需要。App Inventor 基于Apache2 协议开源[1],是自由定制的必要前提,国内外有很多不同的定制版。
安卓系统在2.0版本时引入多点触控,在2.2版本重新设计后成为当前使用的版本。然而 App Inventor 的画布组件一直不支持多点触控,在基于画布组件制作游戏时颇为不便。国内的 WxBit 图形化编程系统在开源App Inventor的基础上实现了完整的多点触控支持,本文分析其中画布组件实现多点触控的方法。
2 分析设计
2.1 触控事件的处理机制
在安卓系统中,用户与APP的交互主要在Activity中完成。用户触碰屏幕时,事件从 Activity到ViewGroup再到View,ViewGroup里面可以嵌套ViewGroup,是一个树形结构,组件结构示意图如图1所示。触控事件产生后,逐级传递,直至被某个View消费,或者被某个ViewGroup拦截消费。简化的事件处理流程如图2所示,在调用onTouchEvent之前还会检查是否应该调用注册的onTouch事件监听[2],因未在画布组件中使用,故省略。
2.2 画布组件的事件处理机制
画布(Canvas)是可见组件,继承自基类 AndroidViewComponent,封装的 CanvasView是自定义的View。CanvasView覆写了onTouchEvent (MotionEvent[3-4] event)方法。从上一节的分析可以知道,触控事件发生在画布的范围时,就会进入 onTouchEvent之中处理。为保证不被上层容器拦截,调用requestDisallowInterceptTouchEvent(true)设为不允许拦截。
在MotionEvent 中,存储着触控点的信息,每个触控点都有PointerIndex(ActionIndex)和PointerId,PointerIndex会随着触控点的数量变化,而只要触控点保持与屏幕接触,PointerId就不会改变。在跟踪触控点时,需要通过 PointerIndex获得PointerId。触控点对 PointerIndex和PointerId 的影响如表1所示。
3 系统实现
3.1 多点触控
基于2.2节的分析可以知道,CanvasView 接收到的触控事件是完整的,画布组件未支持多点触控的原因是原有的实现没有做相对应的处理。当触控点按下时,CanvasView调用画布的TouchDown事件;当触控点持续移动时,调用画布的Dragged事件;当触控点松开时,调用画布的TouchUp事件,之后调用Touched事件。事件的处理时序如图3所示。在其他组件中,也是一样的处理流程。以水平布局和垂直布局增加多点触控支持为例,将Canvas换成HVArrangement,CanvasView 换成HVArrangement中的ViewGroup ,调用ViewGroup的setOnTouchListener设置触控事件的回调函数。
因为PointerId在接触屏幕期间不变,可以用作区分触控事件的标识,变更画布的触控事件定义增加参数pointerId,如图4所示。
画布的Dragged事件中需要用到起点和前点的坐标,在支持多点触控的实现场景下,坐标点信息保存在数组中,以PointerId为索引访问。首先,将用于记录坐标信息的字段定义由单值改为数组,数组长度设置为MAX_FINGER常量,数组定义如图5的A区代码所示。然后,在onTouchEvent中取出触控点编号,再根据编号取出PointerId。设备支持的触控点数量与硬件有关,不一定能达到最大值,为保证不出现数组越界异常,PointerId达到最大值时直接忽略,实现如图5的B区代码所示。
接下来就是根据触控类型对事件进行封装,转发回画布组件中,调用图4定义的代码块,具体实现如图6所示。触控点按下时进入A区,将坐标点信息保存到数组,触发画布和所触碰精灵组件的TouchDown事件;触控点移动时进入B区,当移动的距离小于阈值则忽略,否则触发画布和所触碰精灵组件的Dragged事件;觸控点离开屏幕时进入C区,触发所触碰精灵组件的Touched和TouchUp事件,如果没有触发画布的Dragged事件,则触发Touched事件,最后触发TouchUp事件。
需要注意的是在触控点移动时,getActionIndex不能获得触控点的编号,需要遍历每个触摸点,再调用event.getPointerId以获得对应的PointerId。
3.2 触控手势
多点触控的重要应用就是手势控制,实现非常简单。当两个触控点靠近或远离时,判断为缩放手势,常用于对图片的放大和缩小。安卓SDK中提供了缩放手势的检测类,在重写的方法中增加对画布相应代码块的调用即可,具体实现如图7所示。
当两个触控点围绕某个中心点沿相反的方向移动,判断为旋转手势,常用于对图片的旋转移动等场景。使用的是 Almeros 开源的旋转检测项目[5],具体的实现类似缩放手势,如图8所示。
最后,在画布组件的构造函数中,将缩放手势类和旋转手势类实例化,放进手势检测集合。
4 结束语
本文分析的触控事件的处理机制,在App Inventor系统中都适用。读者如需亲手实现画布组件的多点触控,先要熟悉App Inventor系统的源代码结构和编译方法,详见参考文献[1]链接的README.md文件。其次,还需要对安卓应用开发有一定的了解,能够查阅SDK文档。在某些组件(如按钮及其子类)中注册了onTouch事件监听,优先于onTouchEvent运行,为了让事件能够继续传递,需要返回 false。尽管实现有些差异,原理与本文所描述并不冲突,故本文有较大的参考价值。
参考文献:
[1] MIT App Inventor Public Open Source[EB/OL]. [2021-10-26].https://github.com/mit-cml/appinventor-sources.
[2] Input events overview[EB/OL]. [2021-10-26].https://developer.android.com/guide/topics/ui/ui-events.
[3] MotionEvent[EB/OL].[2021-10-26].https://developer.android.com/reference/android/view/MotionEvent.
[4] POWELL A.Making Sense of Multitouch[EB/OL].[2021-10-26].https://developer.android.com/reference/android/view/MotionEvent.
[5] Gesture detector framework for multitouch handling on Android, based on Android's ScaleGestureDetector[EB/OL].[2021-10-26].https://github.com/Almeros/android-gesture-detectors.
收稿日期:2022-03-16
作者簡介:杨道全(1982—),男,广东雷州人,硕士,研究方向为互联网应用系统与可视化编程技术。