在使用自定义视图的时候,对触屏事件的处理是比不可少的,有能力的可以自己写代码处理,这样更加的灵活。如果不想这么麻烦,Android提供了一个手势监听类GestureDetector,可以供我们使用。GestureDetector使用很方便,提供了单击,双击,长按等操作的处理,但是一般的定义界面都比较复杂,还用很多需要注意的地方,在这儿总结一下GestureDetector的使用。
首先新建一个空白的工程,主界面的layout中只需要添加一个按钮就行
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:paddingBottom="@dimen/activity_vertical_margin" 6 android:paddingLeft="@dimen/activity_horizontal_margin" 7 android:paddingRight="@dimen/activity_horizontal_margin" 8 android:paddingTop="@dimen/activity_vertical_margin" 9 tools:context=".MainActivity" > 10 11 <Button 12 android:id="@+id/btn_textgesture" 13 android:layout_width="fill_parent" 14 android:layout_height="fill_parent" 15 android:text="@string/app_name" /> 16 17 </RelativeLayout>
由于要测试的触屏事件,所有这个按钮比较大,主界面为如下效果:
首先介绍一下触屏事件处理的基本思路。触屏一般有三个基本事件,down按下,move移动,up离开,通过对这三个基本事件的监听,判断用户执行了何种操作。一个标准的触屏操作一般都是一系列基本事件的组合,在Android的框架中,通过onTouch()函数可以获取基本的触屏事件,而像onClick这样的函数,已经是一系列基本事件的组合。
比如,发生了Down事件,在up事件之前没有发生move事件,或者move的范围很小,并且down事件和up事件的间隔很短,这就是一个click或者singelTap事件,
对比实体键盘按键的事件,实体键盘是在down事件发生后进行操作,而触屏事件一般是up事件发生后进行操作。
下面是activity的代码
1 package com.example.testgesture; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.util.Log; 6 import android.view.GestureDetector; 7 import android.view.GestureDetector.SimpleOnGestureListener; 8 import android.view.MotionEvent; 9 import android.view.View; 10 import android.view.View.OnTouchListener; 11 import android.widget.Button; 12 13 public class MainActivity extends Activity { 14 15 private Button mButton; 16 private GestureDetector mGestureDetector; 17 18 @Override 19 protected void onCreate(Bundle savedInstanceState) { 20 super.onCreate(savedInstanceState); 21 setContentView(R.layout.activity_main); 22 23 mGestureDetector = new GestureDetector(this, new MyOnGestureListener()); 24 25 mButton = (Button) findViewById(R.id.btn_textgesture); 26 mButton.setOnTouchListener(new OnTouchListener() { 27 28 @Override 29 public boolean onTouch(View v, MotionEvent event) { 30 Log.i(getClass().getName(), "onTouch-----" + getActionName(event.getAction())); 31 mGestureDetector.onTouchEvent(event); 32 // 一定要返回true,不然获取不到完整的事件 33 return true; 34 } 35 }); 36 } 37 38 private String getActionName(int action) { 39 String name = ""; 40 switch (action) { 41 case MotionEvent.ACTION_DOWN: { 42 name = "ACTION_DOWN"; 43 break; 44 } 45 case MotionEvent.ACTION_MOVE: { 46 name = "ACTION_MOVE"; 47 break; 48 } 49 case MotionEvent.ACTION_UP: { 50 name = "ACTION_UP"; 51 break; 52 } 53 default: 54 break; 55 } 56 return name; 57 } 58 59 class MyOnGestureListener extends SimpleOnGestureListener { 60 @Override 61 public boolean onSingleTapUp(MotionEvent e) { 62 Log.i(getClass().getName(), "onSingleTapUp-----" + getActionName(e.getAction())); 63 return false; 64 } 65 66 @Override 67 public void onLongPress(MotionEvent e) { 68 Log.i(getClass().getName(), "onLongPress-----" + getActionName(e.getAction())); 69 } 70 71 @Override 72 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 73 Log.i(getClass().getName(), 74 "onScroll-----" + getActionName(e2.getAction()) + ",(" + e1.getX() + "," + e1.getY() + ") ,(" 75 + e2.getX() + "," + e2.getY() + ")"); 76 return false; 77 } 78 79 @Override 80 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 81 Log.i(getClass().getName(), 82 "onFling-----" + getActionName(e2.getAction()) + ",(" + e1.getX() + "," + e1.getY() + ") ,(" 83 + e2.getX() + "," + e2.getY() + ")"); 84 return false; 85 } 86 87 @Override 88 public void onShowPress(MotionEvent e) { 89 Log.i(getClass().getName(), "onShowPress-----" + getActionName(e.getAction())); 90 } 91 92 @Override 93 public boolean onDown(MotionEvent e) { 94 Log.i(getClass().getName(), "onDown-----" + getActionName(e.getAction())); 95 return false; 96 } 97 98 @Override 99 public boolean onDoubleTap(MotionEvent e) { 100 Log.i(getClass().getName(), "onDoubleTap-----" + getActionName(e.getAction())); 101 return false; 102 } 103 104 @Override 105 public boolean onDoubleTapEvent(MotionEvent e) { 106 Log.i(getClass().getName(), "onDoubleTapEvent-----" + getActionName(e.getAction())); 107 return false; 108 } 109 110 @Override 111 public boolean onSingleTapConfirmed(MotionEvent e) { 112 Log.i(getClass().getName(), "onSingleTapConfirmed-----" + getActionName(e.getAction())); 113 return false; 114 } 115 } 116 }
首先是声明一个GestureDetector,然后重写Button的onTouch函数,将触屏事件交给GestureDetector处理。
首先做一个对按钮做一个单击
onSingleTapUp被调用,说明发生了单击事件,onSingleTapConfirmed被调用,说明确认发生了一个单击事件,不是双击的事件。需要注意的是onSingleTapUp已经是一click事件,onSingleTapUp触发的时候是ACTION_UP事件。onSingleTapConfirmed是在用户手指离开屏幕后触发,所有up并不是所有触屏事件的结束。
做一个双击的操作
首先发生了一个onSingleTapUp,说明完成了一次单击事件,然后发生了onDoubleTap,至此,一次双击事件已经完成。我们可以看到,onDoubleTap发生的时候是ACTION_DOWN事件,也就是说双击事件是第二次按下屏幕的时候触发,而不是第二次离开屏幕的时候触发,在onDoubleTap发生之后,就可以在onDoubleTapEvent中监听到双击事件发生后从按下到弹起的所有触屏事件。onDoubleTap发生后不会触发onSingleTapUp和onSingleTapConfirmed。
做一个长按的操作
onLongPress实在ACTION_DOWN时发生,onLongPress发生后在up之前不会用其他事件触发,可以在onShowPress处理状态的改变,比如按钮的按下状态。
做一个滑动操作
onScroll事件是拖动,onFling是抛。结合log来了解一下。首先是ACTION_DOWN,之后多次ACTION_MOVE,移动超过一定距离,触发了onScroll,如果onScroll被触发,在up之前是不会有长按,单击,双击等事件的。看一下onScroll的参数
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
e1为第一次按下的事件,和onDown事件里面的一样,e2为当前的事件,distanceX为本次onScroll移动的X轴距离,distanceY为移动的Y轴距离,移动的距离是相对于上一次onScroll事件的移动距离,而不是当前点和按下点的距离。
这次滑动最后触发了onFling事件,但是onFling事件的触发不是一定的,onFling是在ACTION_UP触发,平时列表在离开屏幕是继续滚动,就是通过这种方式触发。
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
onFling的前两个参数和onScroll相同,e2为用户拖动完离开屏幕时的点。veloctiyX,velocitY为离开屏幕时的初始速度,以这两个速度为初始速度做匀减速运动,就是现在拖动列表和拖动图片的各种缓存滚动的效果。
函数的返回值
除了onLongPress,这些函数都是有返回值的,
1 mButton.setOnTouchListener(new OnTouchListener() { 2 3 @Override 4 public boolean onTouch(View v, MotionEvent event) { 5 Log.i(getClass().getName(), "onTouch-----" + getActionName(event.getAction())); 6 mGestureDetector.onTouchEvent(event); 7 // 一定要返回true,不然获取不到完整的事件 8 return true; 9 } 10 });
这些返回值会通过 mGestureDetector.onTouchEvent(event); 传递给onTouch。
最后总结一下
GestureDetector结合SimpleOnGestureListener可以很方便的获取到单击,双击,长按等事件,但是对这些事件的处理不是简单的在对应的函数里做一些操作就可以的,复杂的自定义视图还是要在onTouch里面进行判断个个控件的焦点状态,而且GestureDetector也不是万能的,你如果要处理长按之后的移动,就要费一番功夫了,以为GestureDetector在长按发生后是不会在有onScroll的,你只能通过onTouch里面的ACTION_MOVE处理。