Android的手势识别中,onGestureListener要与onTouchListener配合使用,因为只有onTouchListener才是实际的监听到用户的触摸行为,它把它所有监听到的用户触摸通知"手势识别"类对象(GestureDetector),手势识别类对象处理后触发对应的函数,例如onFling, onLongPress等等。
基础
GestureDetector的工作原理是当我们接收到用户触摸消息时,将这个消息交给GestureDetector去加工,我们通过设置侦听器获得GestureDetector处理后的手势。GestureDetector提供了两个侦听器接口,OnGestureListener处理单击类消息,OnDoubleTapListener处理双击类消息。
OnGestureListener的接口有这几个:
// 单击,触摸屏按下时立刻触发
abstract boolean onDown(MotionEvent e); // 抬起,手指离开触摸屏时触发(长按、滚动、滑动时,不会触发这个手势) abstract boolean onSingleTapUp(MotionEvent e); // 短按,触摸屏按下后片刻后抬起,会触发这个手势,如果迅速抬起则不会 abstract void onShowPress(MotionEvent e); // 长按,触摸屏按下后既不抬起也不移动,过一段时间后触发 abstract void onLongPress(MotionEvent e); // 滚动,触摸屏按下后移动 abstract boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY); // 滑动,触摸屏按下后快速移动并抬起,会先触发滚动手势,跟着触发一个滑动手势 abstract boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
OnDoubleTapListener的接口有这几个:
// 双击,手指在触摸屏上迅速点击第二下时触发
abstract boolean onDoubleTap(MotionEvent e); // 双击的按下跟抬起各触发一次 abstract boolean onDoubleTapEvent(MotionEvent e); // 单击确认,即很快的按下并抬起,但并不连续点击第二下 abstract boolean onSingleTapConfirmed(MotionEvent e);
有时候我们并不需要处理上面所有手势,方便起见,Android提供了另外一个SimpleOnGestureListener实现了如上的OnGestureListener和onTouchListener接口,我们只需要继承SimpleOnGestureListener然后重载感兴趣的手势即可。
如下为OnGestureListener和onTouchListener使用的例子:
public class ViewFliperActivity extends Activity implements OnTouchListener, OnGestureListener { private ViewFlipper mFlipper; private GestureDetector mGestureDetector; private int mCurrentLayoutState; private static final int FLING_MIN_DISTANCE = 100; private static final int FLING_MIN_VELOCITY = 200; private static int currentNum = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.view_flipper); mFlipper = (ViewFlipper) findViewById(R.id.flipper); // 注册一个用于手势识别的类 mGestureDetector = new GestureDetector(this); mGestureDetector.setIsLongpressEnabled(false); // 给mFlipper设置一个listener mFlipper.setOnTouchListener(this); mCurrentLayoutState = 0; // 允许长按住ViewFlipper,这样才能识别拖动等手势 mFlipper.setLongClickable(true); } public void switchLayoutStateTo(int switchTo) { while (mCurrentLayoutState != switchTo) { if (mCurrentLayoutState > switchTo) { mCurrentLayoutState--; mFlipper.setInAnimation(inFromLeftAnimation()); mFlipper.setOutAnimation(outToRightAnimation()); mFlipper.showPrevious(); } else { mCurrentLayoutState++; mFlipper.setInAnimation(inFromRightAnimation()); mFlipper.setOutAnimation(outToLeftAnimation()); mFlipper.showNext(); } } ; } protected Animation inFromRightAnimation() { Animation inFromRight = new TranslateAnimation( Animation.RELATIVE_TO_PARENT, +1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f); inFromRight.setDuration(500); inFromRight.setInterpolator(new AccelerateInterpolator()); return inFromRight; } protected Animation outToLeftAnimation() { Animation outtoLeft = new TranslateAnimation( Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f); outtoLeft.setDuration(500); outtoLeft.setInterpolator(new AccelerateInterpolator()); return outtoLeft; } protected Animation inFromLeftAnimation() { Animation inFromLeft = new TranslateAnimation( Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f); inFromLeft.setDuration(500); inFromLeft.setInterpolator(new AccelerateInterpolator()); return inFromLeft; } protected Animation outToRightAnimation() { Animation outtoRight = new TranslateAnimation( Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, +1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f); outtoRight.setDuration(500); outtoRight.setInterpolator(new AccelerateInterpolator()); return outtoRight; } public boolean onDown(MotionEvent e) { return false; } public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if ((e1.getX() - e2.getX()) > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) { if(currentNum<mFlipper.getChildCount()){ mFlipper.setInAnimation(inFromRightAnimation()); mFlipper.setOutAnimation(outToLeftAnimation()); mFlipper.showNext(); currentNum++; } } else if ((e2.getX() - e1.getX()) > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) { if(currentNum>1){ // 当像右侧滑动的时候 mFlipper.setInAnimation(inFromLeftAnimation()); mFlipper.setOutAnimation(outToRightAnimation()); mFlipper.showPrevious(); currentNum--; } } return false; } public void onLongPress(MotionEvent e) { } public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } public void onShowPress(MotionEvent e) { } public boolean onSingleTapUp(MotionEvent e) { return false; } public boolean onTouch(View v, MotionEvent event) { // 一定要将触屏事件交给手势识别类去处理(自己处理会很麻烦的) return mGestureDetector.onTouchEvent(event); }
注意问题:
两个解决办法:
1) 修改下面的代码,return true
这个方法同样也可以解决一般触摸屏事件的冲突问题,例如click和LongPress,在处理这个事件后,需要允许后继事件。
@Override public boolean onDown(MotionEvent e) { return false; }
2)设置当前的view
this.setLongClickable(true);
2.长按后滑动无效
GestureDetector默认是打开LongPress通知的,但是有个问题,长按后,手不离开屏幕且滑动,这个时候发现没有滑动事件。而在monolith的HomeScreen中,需要的正是长按之后的滑动,长按不需要。这个问题的解决办法是设置手势识别对象,禁止产生长按事件。
mGestureDetector.setIsLongpressEnabled(false);
当然,没有禁用长按事件,滑动事件还是有效的,只是要确保触屏后马上滑动,不要等系统产生了长按。
3. 必须在View的onTouchListener中调用手势识别,而不能像Activity一样重载onTouchEvent,否则同样手势识别无法正确工作。