GLSurfaceView中处理手势

来源:互联网 发布:淘宝高跟鞋 编辑:程序博客网 时间:2024/03/29 18:39

图书地址:http://item.jd.com/11163164.html

级联触摸事件

触摸事件在处理方法上类似按键事件。当用户触摸设备屏幕时,Java 的Activity通过重载onTouchEvent 方法来接收MotionEvent对象。该事件包含单击的坐标,原点(0,0) 在屏幕左上角。通过调用event.getAction()方法能获得事件类型(ACTION_DOWN 、ACTION_UP和ACTION_MOVE)。基于这个值保存开始的 X 和Y 坐标值。最后,当拖动手指时计算出X 和Y 的增量(dx,dy) 并将它们发送到原生层进行处理。当手指抬起时,开始的X 和Y 坐标被重置,如代码清单2-12 所示,最终效果是原生引擎能够使用这一系列的X 和Y 增量在三维空间中移动角色或环顾四周环境。在Quake 中就是这样处理运动的。

代码清单2-12   级联Java和C 之间的触摸事件

//Java:需要在C 中实现的原生方法public static native int mouseLook(int deltaX, int deltaY); public static native int mouseMove(int deltaX, int deltaY); //按键按下的开始坐标float startX = -1, startY = -1; public boolean onTouchEvent(MotionEvent event)  {     int action = event.getAction();     if ( action == MotionEvent.ACTION_DOWN ) {         startX = event.x;         startY = event.y;      } else if ( action == MotionEvent.ACTION_UP )  { startX = startY = 0;     }     else if ( action == MotionEvent.ACTION_MOVE)      {         final float dx = event.x - startX;          final float dy = event.y - startY;                   //  取决于移动或观察        mouseLook(dx , dy );         //mouseMove (dx, dy);     }     return true;  }  // int mouseLook(int deltaX, int deltaY) 方法的C 实现JNIEXPORT jint JNICALL Java_Natives_mouseMove   (JNIEnv * env, jclass cls, jint dx, jint dy) {       LOGD("Mouse Move %d, %d", dx, dy); } // int mouseLook(int deltaX, int deltaY) 方法的C 实现 JNIEXPORT jint JNICALL Java_Natives_mouseLook   (JNIEnv * env, jclass cls, jint dx, jint dy) {       LOGD("Mouse Look %d, %d", dx, dy); }

前面我们看到的是单点触摸事件,可能无法满足某些游戏的需求,比如第一人称射击类游戏,玩家需要同时移动和瞄准。接下来的内容可能会帮助我们解决这些问题。多点触摸是扩展了单点触摸API 以提供多个手指同时操作的一种技术,可以运用于游戏中的复杂交互。

2.5  多点触摸

Android的多点触摸功能是对MotionEvent的扩展,包含所有你需要实现多点触摸模式的信息。例如,假设有个游戏需要向左边滑动手指以实现将三维空间的角色向前或一边移动,向右滑动则环顾四周。使用Android的MotionEvent能够轻松地实现这样的模式。考虑3 个简单类——MultiTouchGesture、MultiTouchScreen和TestActivity ,分别如代码清单2-13 、2-14 、2-15 所示。这些都会在以下内容中讨论。

2.5.1  MultiTouchGesture

这个类封装了一些手势类型,如移动或查看角色( 如代码清单2-13 所示) 。它还定义手势在屏幕上的有效区域( 依赖Android的Rect 类) 。如果手势有效,就将执行一些动作( 比如将移动或观察的增量发送到原生引擎)。

代码清单2-13   多点触摸手势package com.touch; import android.graphics.Point; import android.graphics.Rect; import android.view.MotionEvent;  public class MultiTouchGesture {   public enum eGestureType {  MOVE, LOOK };    Rect bounds;   eGestureType type;    public MultiTouchGesture(eGestureType type, Rect bounds) {     this.type = type;     this.bounds = bounds;   } /**    *  执行手势   * @param action    *          {@link MotionEvent} action: ACTION_UP, ACTION_MOVE,...    * @param p    *          手指的X、Y 坐标   */   public boolean execute(int action, Point p) {     switch (type) {     case MOVE:       doMove(action, p);       break;      case LOOK:       doLook(action, p);       break;      default:       break;     }     return true;   }  public void reset() {     switch (type) {     case MOVE:        break;     case LOOK:       break;      default:       break;     }   } private void doMove(int action, Point p) {     //  告诉原生引擎执行"移动"操作   } private void doLook(int action, Point p) {     //  告诉原生引擎执行"环顾四周"操作  } }

MultiTouchGesture 根据手势类型来执行对应的操作( 这里是MOVE 或LOOK)。根据不同的类型触发不同的原生方法,向游戏引擎发送 X 和Y 坐标。action 参数表示手势被什么类型的MotionEvent触发,可以是以下类型:
MotionEvent.ACTION_DOWN (first finger) 
MotionEvent.ACTION_UP (first finger) 
MotionEvent.ACTION_MOVE (first finger) 
ACTION_POINTER_1_DOWN (second) 
ACTION_POINTER_1_UP (second) 
ACTION_POINTER_2_DOWN (third) 
ACTION_POINTER_2_UP (third) 
ACTION_POINTER_3_DOWN (fourth) 
ACTION_POINTER_3_UP (fourth) 

提示:

Android可以支持多达4 个点或手指在屏幕上。

该类的作用是存储手势列表、检查边界以及与主Activity 进行交互( 如代码清单 2-14
所示)。

代码清单2-14  MultiTouchScreen package com.touch;  import java.util.ArrayList; import android.graphics.Point; import android.view.MotionEvent; public class MultiTouchScreen {   private ArrayList<MultiTouchGesture> mGestures;    /**    *  构造函数    * @param gestures    */   public MultiTouchScreen(ArrayList<MultiTouchGesture> gestures) {     mGestures = gestures;   }    /**    *  触摸事件。当用1 个手指时模拟压力事件   * @param e    */   public void onTouchEvent(MotionEvent e) {     final int action = e.getAction();     int count = e.getPointerCount();Point[] points = new Point[count];   // 提取每个手指的坐标    for (int i = 0; i < points.length; i++) {       points[i] = new Point((int) e.getX(i), (int) e.getY(i));     }      //  遍历每个手势    for (MultiTouchGesture g : mGestures) {       //  每个手指 (pointer)       for (int j = 0; j < count; j++) {         if (g.bounds.contains(points[j].x, points[j].y)) {           g.execute(action, points[j]);         }       }     }          //  在手指抬起时重置    if (action == MotionEvent.ACTION_UP) {       for (MultiTouchGesture g : mGestures) {         g.reset();       }     }   } }

MultiTouchScreen类接收从主Activity传来的MotionEvent并检查每个手势的X 和Y坐标是否在有效区域内。如果是,手势就将被执行。可以通过下面的代码从MotionEvent中获得点的数量:
int count = e.getPointerCount(); 

然后,每个点的坐标可以通过遍历点的数量并提取它们的X 和Y 坐标来获得:
Point[] points = new Point[count]; 
 
//  提取每个手指的坐标
for (int i = 0; i < points.length; i++) { 
  points[i] = new Point((int) e.getX(i), (int) e.getY(i)); 
    }

最后,我们通过遍历每个手势来检查( 按下的) 点是否在手势的有效区域内以及每个点的X 和Y 坐标是否在手势的边界矩形内:
 
for (MultiTouchGesture g : mGestures) { 
   // 每个手指(pointer) 
   for (int j = 0; j < count; j++) { 
     if (g.bounds.contains(points[j].x, points[j].y)) { 
       g.execute(action, points[j]); 
     } 
   } 


最后,我们需要使用 Activity来初始化多点触摸屏幕、手势边界以及监听触摸事件( 如代码清单2-15 所示)。

代码清单2-15  MultiTouchScreen类的TestActivitypublic class TestActivity extends Activity { MultiTouchScreen mtScreen;   @Override   public void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.main);      //  初始化MultiTouchScreen    ArrayList<MultiTouchGesture> gestures = new ArrayList<MultiTouchGesture>();      int w = getWindow().getWindowManager().getDefaultDisplay().getWidth();     int h = getWindow().getWindowManager().getDefaultDisplay().getHeight();      //  移动: 范围为左半边屏幕    gestures.add(      new MultiTouchGesture(eGestureType.MOVE, new Rect(0, 0, w / 2, h)));      //  查看:范围为右半边屏幕    gestures.add(         new MultiTouchGesture(eGestureType.LOOK, new Rect(w / 2, 0, w, h)));      mtScreen = new MultiTouchScreen(gestures);   }    @Override   public boolean onTouchEvent(MotionEvent event) {     mtScreen.onTouchEvent(event);     return true;   } }

TestActivity通过获得显示屏的宽度和高度( 使用getWindow().getWindowManager().
getDefaultDisplay())来初始化手势的坐标。然后初始化两种类型的手势:左半边屏幕为MOVE 手势,右半边屏幕为 LOOK 手势。这些手势被传递到 MultiTouchScreen类的构造函数。最后当单点或多点事件触发时,主 Activity中的onTouchEvent 方法会被调用,然后将事件传递到MultiTouchScreen进行处理。这就允许我们的游戏角色在3D 空间中能同时执行移动和查看操作。这个技术将在本书的第6 章和第7 章中得到测试。

多点触摸的注意事项

思考在游戏中实现复杂的多点触摸模式之前,你应该明白:旧设备上Android的多点触摸API 拥有非常严重的错误(充满了假点和错误坐标) ,比如Motorola Droid 1和第一代手机。当你用两个手指在屏幕上触摸或拖动并简单地转储MotionEvent的坐标时,将得到大量的错误坐标和假点,这样的问题令人非常沮丧,尤其是用在像Quake 一样的3D 射击游戏中。也许这就是3D射击游戏无法在Android Market中存在的原因( 译者注:Android Market已经更名为Google Play)。目前并不清楚这些bug 的原因。也许是因为廉价的硬件或有问题的内核驱动。但是值得高兴的是在第二代和第三代设备上,如 Droid 3,这个问题已经得到显著改善。在最新的 Android SDK 中,多点触摸驱动已经得到进一步的改善,如果你有还不错的硬件设备,就再也不会遇到这类多点触摸的问题了。但是在廉价的硬件和老的Android版本里仍要注意。

我们已经介绍了一些巧妙地处理视频、音频、使用键盘和单点与多点触摸的技巧。但如果你是铁杆玩家,就可能会经常听到抱怨——用触摸界面或很小的按键来玩操作性强的游戏( 如射击游戏) 非常困难。试着对比用触摸屏和控制器玩 Doom,你将会感到沮丧并很快厌烦触摸屏。一些游戏仅仅需要控制器,这就是接下来要介绍的蓝牙控制器。我相信如果你的游戏提供对蓝牙控制器的支持,那么用户将会很高兴。