使用 App Inventor 扩展实现多点触控:旋转检测器
草稿(2016 年 3 月 6 日):构建扩展需要 App Inventor 扩展 功能,该功能尚未纳入 App Inventor 版本。但您可以尝试一些 ofary AI2 服务器。
您可以使用扩展测试发送 (2) 实现下面第 2 部分中所述的 RotationDetector.aix 扩展,随着系统的发展,可能需要重建。
内容
请记住,扩展机制仍然不稳定且处于开发阶段。您在步骤 (1) a 中构建的任何 aia 文件
使用 App Inventor 扩展 实现多点触控:旋转检测器
带有旋转检测器扩展组件的演示应用程序
如何构建旋转检测器扩展
App Inventor 没有内置多点触控功能。添加多点触控是一个常见的要求,MIT App Inventor 最终可能会将其包含在系统中。本说明说明了如何使用 App Inventor 扩展 实现 RotationDetector 组件,人们可以使用它来创建对双指旋转手势做出反应的应用程序。
本说明在两个方面很有趣:
对于 App Inventor 用户,它提供了一个可以共享和合并到项目中的功能齐全的旋转检测器组件。
对于 App Inventor 开发人员,它可以作为通用旋转检测器组件的模板,可以对其进行修改以满足特定要求。
此处多点触控的实现方法如下:使用 App Inventor 扩展 实现多点触控:Scale Detector,其中包含对该技术的更详细说明。在构建旋转检测器之前,先完成该示例会很有用。
使用 RotationDetector 扩展组件的演示应用程序
旋转检测器使程序员能够检测在画布上执行的双指旋转手势。此处的演示应用程序称为 rotationSprite,是检测旋转手势的简单演示。您可以从此处下载演示应用程序的 apk 文件。演示应用程序仅说明如何使用 RotationDetector 组件在画布上旋转 ImageSprite。此演示应用程序的设计器视图和块视图如下所示。
以下是实现 rotationSprite 应用的方法:
在 Screen1 上,有一个 Canvas,即 Canvas1,其中包含我们要旋转的 ImageSprite(ArrowSprite)。Label1 将以文本格式显示箭头的航向。航向 0 指向右侧。我们还添加了一个 RotationDetector(MyRotationDetector1)作为不可见组件。
使用 Rotation Detector 的第一个关键部分是通过调用方法 MyRotationDetector1.AddHandlerToCanvas 将画布与其关联,该方法配置检测器以监听 Canvas1 上的手指移动。此步骤应在屏幕初始化时完成。
RotationDetector 组件的主要功能是事件处理程序 Rotate,它使用数字角度进行调用。该变量表示两根手指旋转的角度(以弧度[a] 为单位)。当角度小于 0 时,表示手指顺时针旋转;当角度大于 0 时,表示手指逆时针旋转。它会根据返回的角度更改 ArrowSprite 的航向。在 Rotate 结束时,它会更新 Label1 中的文本以显示 ArrowSprite 的当前航向。
您可以使用 RotationDetector 扩展组件创建自己的响应旋转手势的多点触控应用。您需要按照 App Inventor 扩展 中的说明导入 MyRotationDetector.aix 扩展文件,然后使用 MyRotationDetector 组件进行构建,就像使用任何组件一样。 MyRotationDetector.aix 扩展可在此处获取:https://drive.google.com/drive/folders/0B3jsksMcCW5bLVZCNjZmQ3FSS2M
我们还提供了此演示应用的完整 aia 源代码,以节省您从头开始组装块的工作量。如果您将此 aia 文件加载到 App Inventor,则无需单独安装 MyRotationDetector.aix 扩展。扩展包含在源代码中。一般来说,项目源文件包括项目使用的任何扩展。
如何构建旋转检测器扩展
上一节展示了如何使用提供的 MyRotationDetector 扩展 aix 文件创建应用。您还可以练习实现或修改扩展本身。
MyRotationDetector 扩展以 Java 代码实现,该代码经过编译和处理以生成 aix 文件,如 App Inventor 扩展 中所述。 Java 代码文件 MyRotationDetector.java 可在此处获取:https://drive.google.com/open?id=0B3jsksMcCW5bUU1rOGlNSGpTVE0
与ScaleDetector不同,Android SDK没有内置的旋转检测器可供使用。因此,在为App Inventor实现扩展组件之前,我们需要实现一个旋转检测器模块,它可以检测旋转并计算旋转的角度。我们使用以下代码实现旋转检测器:
public class RotationDetector {
protected final Context mContext;
private static final int INVALID_POINTER_ID = -1;
private float fX, fY, sX, sY,oldval,newval;
private int ptrID1, ptrID2;
private float mAngle;
private OnRotationGestureListener mListener;
public float getAngle() {
return mAngle;
}
public RotationDetector(Context c, OnRotationGestureListener listener){
mContext = c;
mListener = listener;
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
}
public boolean onTouchEvent(MotionEvent event){
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
ptrID1 = event.getPointerId(event.getActionIndex());
break;
//Detect the second finger pointer
case MotionEvent.ACTION_POINTER_DOWN:
ptrID2 = event.getPointerId(event.getActionIndex());
sX = event.getX(event.findPointerIndex(ptrID1));
sY = event.getY(event.findPointerIndex(ptrID1));
fX = event.getX(event.findPointerIndex(ptrID2));
fY = event.getY(event.findPointerIndex(ptrID2));
oldval = sX+sY+fY+fX;
break;
case MotionEvent.ACTION_MOVE:
Log.i("Rotate","Action Move");
if(ptrID1 != INVALID_POINTER_ID && ptrID2 != INVALID_POINTER_ID){
float nfX, nfY, nsX, nsY;
nsX = event.getX(event.findPointerIndex(ptrID1));
nsY = event.getY(event.findPointerIndex(ptrID1));
nfX = event.getX(event.findPointerIndex(ptrID2));
nfY = event.getY(event.findPointerIndex(ptrID2));
newval = nsX+nsY+nfX+nfY;
//Create a buffer for detection.
//Otherwise, even the tiny change will trigger the event
if(Math.abs(newval-oldval)>5){
Log.i("Rotate", "Fire Rotation");
mAngle = angleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
if (mListener != null) {
mListener.OnRotation(this);
}
oldval = newval;
}
}
break;
case MotionEvent.ACTION_UP:
ptrID1 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
ptrID2 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_CANCEL:
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
break;
}
return true;
}
private float angleBetweenLines (float fX, float fY, float sX, float sY, float nfX, float nfY, float nsX, float nsY)
{
float v1x = fX-sX;
float v1y = -(fY-sY);
float v2x = nfX-nsX;
float v2y = -(nfY-nsY);
float angle1 = normalizeAngle((float) Math.atan2(v1y, v1x),v1x,v1y);
float angle2 = normalizeAngle((float) Math.atan2(v2y, v2x),v2x,v2y);
Log.i("Rotate", String.format("Vector1 (%f,%f) Vector2 (%f,%f)", v1x,v1y,v2x,v2y));
float angle = (float)Math.toDegrees(angle1-angle2);
return angle;
}
private float normalizeAngle(float angle,float x, float y){
if(x>=0&&y>=0){
return angle;
}else if(x>=0&&y<0){
return angle;
}else if(x<0&&y>=0){
return (float)Math.PI-angle;
}else{
return -(float)Math.PI+angle;
public class RotationDetector {
protected final Context mContext;
private static final int INVALID_POINTER_ID = -1;
private float fX, fY, sX, sY,oldval,newval;
private int ptrID1, ptrID2;
private float mAngle;
private OnRotationGestureListener mListener;
public float getAngle() {
return mAngle;
}
public RotationDetector(Context c, OnRotationGestureListener listener){
mContext = c;
mListener = listener;
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
}
public boolean onTouchEvent(MotionEvent event){
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
ptrID1 = event.getPointerId(event.getActionIndex());
break;
//Detect the second finger pointer
case MotionEvent.ACTION_POINTER_DOWN:
ptrID2 = event.getPointerId(event.getActionIndex());
sX = event.getX(event.findPointerIndex(ptrID1));
sY = event.getY(event.findPointerIndex(ptrID1));
fX = event.getX(event.findPointerIndex(ptrID2));
fY = event.getY(event.findPointerIndex(ptrID2));
oldval = sX+sY+fY+fX;
break;
case MotionEvent.ACTION_MOVE:
Log.i("Rotate","Action Move");
if(ptrID1 != INVALID_POINTER_ID && ptrID2 != INVALID_POINTER_ID){
float nfX, nfY, nsX, nsY;
nsX = event.getX(event.findPointerIndex(ptrID1));
nsY = event.getY(event.findPointerIndex(ptrID1));
nfX = event.getX(event.findPointerIndex(ptrID2));
nfY = event.getY(event.findPointerIndex(ptrID2));
newval = nsX+nsY+nfX+nfY;
//Create a buffer for detection.
//Otherwise, even the tiny change will trigger the event
if(Math.abs(newval-oldval)>5){
Log.i("Rotate", "Fire Rotation");
mAngle = angleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
if (mListener != null) {
mListener.OnRotation(this);
}
oldval = newval;
}
}
break;
case MotionEvent.ACTION_UP:
ptrID1 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
ptrID2 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_CANCEL:
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
break;
}
return true;
}
private float angleBetweenLines (float fX, float fY, float sX, float sY, float nfX, float nfY, float nsX, float nsY)
{
float v1x = fX-sX;
float v1y = -(fY-sY);
float v2x = nfX-nsX;
float v2y = -(nfY-nsY);
float angle1 = normalizeAngle((float) Math.atan2(v1y, v1x),v1x,v1y);
float angle2 = normalizeAngle((float) Math.atan2(v2y, v2x),v2x,v2y);
Log.i("Rotate", String.format("Vector1 (%f,%f) Vector2 (%f,%f)", v1x,v1y,v2x,v2y));
float angle = (float)Math.toDegrees(angle1-angle2);
return angle;
}
private float normalizeAngle(float angle,float x, float y){
if(x>=0&&y>=0){
return angle;
}else if(x>=0&&y<0){
return angle;
}else if(x<0&&y>=0){
return (float)Math.PI-angle;
}else{
return -(float)Math.PI+angle;
}
}
}
基本思路是,如果屏幕上有两个指针,它们可以形成一条线。如果它们移动,它们将形成一条新线。我们可以计算这两条线之间的角度来检测它是否是旋转检测器。角度也可用于确定旋转的方向。
除了 RotationDetector,我们还创建了一个接口 OnRotationGestureListener。
public static interface OnRotationGestureListener {
public void OnRotation(RotationDetector rotationDetector);
}
一旦我们有了旋转检测器,我们就可以实现扩展组件。我们创建一个简单的事件 Rotate,它允许用户创建旋转事件的处理程序。
@SimpleEvent
public void Rotate(double angle) {
EventDispatcher.dispatchEvent(this, "Rotate", angle);
}
我们还创建了 MyOnRotationGestureListener,这是一个实现 OnRotationGestureListener 的类。在其 OnRotation 块中,它检测旋转手势,并将角度作为我们的 Rotate 事件的回调参数返回。
// Here's the gesture listener.
public class MyOnRotationGestureListener implements
OnRotationGestureListener {
@Override
public void OnRotation(RotationDetector rotationDetector) {
float angle = rotationDetector.getAngle();
Rotate((double) angle);
}
}
接下来,我们创建新的手势检测器类,该类扩展了我们上面创建的 RotationDetector,用于添加到 Canvas。它由手势侦听器以及应该发生侦听的上下文构建而成:
public class ExtensionRotationDetector extends RotationDetector
implements Canvas.ExtensionGestureDetector {
public ExtensionRotationDetector(Context c, OnRotationGestureListener l ) {
super(c,l);
}
}
这里的关键步骤是将类声明为实现 Canvas.ExtensionDetector。Canvas.ExtensionDetector 是 Canvas 提供的一个接口。每个 Canvas 都有一个手势处理器列表,当 Canvas 被触摸时会调用这些处理器。可以使用 Canvas.registerCustomGestureDetector 将实现 ExtensionDetector 接口的类添加到 Canvas 的列表中。
最后,我们创建了函数 AddHandlerToCanvas:
@SimpleFunction
public void AddHandlerToCanvas(Canvas myCanvas) {
this.myCanvas = myCanvas;
ExtensionRotationDetector myDetector =
new ExtensionRotationDetector(myCanvas.getContext(), new MyOnRotationGestureListener());
myCanvas.registerCustomGestureDetector(myDetector);
}
这里我们创建 ExtensionRotationDetector 的一个实例并将其注册到 myCanvas。
您可以修改源代码以满足您自己的需求。
资源下载
MyRotationDetector拓展下载:
edu.mit.appinventor.MyRotationDetector.aix
源码下载:
edu.mit.appinventor.MyRotationDetector.zip
demo下载: