本文转自:http://trinea.iteye.com/blog/1054786
http://blog.youkuaiyun.com/lonelyroamer/article/details/7558313
手机进入智能机时代,触摸屏也已成为主流之势,原来的手机按键也被屏幕点触取代,滑动屏幕操作则相对屏幕点击更能获得用户的青睐,习惯了各种浏览器的鼠标手势、pad等平板的切滑、类似iReader的软件丰富的手势后,是不是也想自己的软件能够用食指炫起来呢,下面就让我们来看看android的手势操作吧
先介绍下左右滑动切换Activity,对于复杂的手势原理一样,具体后述。
主要原理为监控触屏事件和手势事件,在触屏事件处理函数中调用手势事件处理函数,表示用户触屏后是否有手势操作,有则进行手势事件处理,大致分为四步
1、需要继承OnGestureListener和OnDoubleTapListener,如下:
- public class ViewSnsActivity extends Activity implements OnTouchListener, OnGestureListener
这两个类分别是触屏监听器和手势监控器,具体可查看OnTouchListener和OnGestureListener
2、在添加mGestureDetector的定义,并在ViewSnsActivity的onCreate函数中加入其页面布局的setOnTouchListener事件
- GestureDetector mGestureDetector;
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.view_sns_activity);
- mGestureDetector = new GestureDetector((OnGestureListener) this);
- LinearLayout viewSnsLayout = (LinearLayout)findViewById(R.id.viewSnsLayout);
- viewSnsLayout.setOnTouchListener(this);
- viewSnsLayout.setLongClickable(true);
- }
mGestureDetector为手势监听对象,下面的OnFling就是为其实现,用来处理手势的
viewSnsLayout.setOnTouchListener(this);表示viewSnsLayout这个layout的触屏事件由下面的OnTouch处理
3、重载onFling函数
- private int verticalMinDistance = 20;
- private int minVelocity = 0;
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- if (e1.getX() - e2.getX() > verticalMinDistance && Math.abs(velocityX) > minVelocity) {
- // 切换Activity
- // Intent intent = new Intent(ViewSnsActivity.this, UpdateStatusActivity.class);
- // startActivity(intent);
- Toast.makeText(this, "向左手势", Toast.LENGTH_SHORT).show();
- } else if (e2.getX() - e1.getX() > verticalMinDistance && Math.abs(velocityX) > minVelocity) {
- // 切换Activity
- // Intent intent = new Intent(ViewSnsActivity.this, UpdateStatusActivity.class);
- // startActivity(intent);
- Toast.makeText(this, "向右手势", Toast.LENGTH_SHORT).show();
- }
- return false;
- }
OnFling的四个参数意思分别为
- e1 The first down motion event that started the fling.手势起点的移动事件
- e2 The move motion event that triggered the current onFling.当前手势点的移动事件
- velocityX The velocity of this fling measured in pixels per second along the x axis.每秒x轴方向移动的像素
- velocityY The velocity of this fling measured in pixels per second along the y axis.每秒y轴方向移动的像素
说的更简单点就是,鼠标手势相当于一个向量(当然有可能手势是曲线),e1为向量的起点,e2为向量的终点,velocityX为向量水平方向的速度,velocityY为向量垂直方向的速度
- if (e1.getX() - e2.getX() > verticalMinDistance && Math.abs(velocityX) > minVelocity)
则上面的语句能知道啥意思了吧,就是说向量的水平长度必须大于verticalMinDistance,并且水平方向速度大于minVelocity
从而我们可以如此判断手势是否满足一定的条件从而进行相应响应,也可以根据这个写出更复杂的手势判断。
4、重载onTouch函数
在2中我们定义了viewSnsLayout的touch事件处理,下面我们来实现,直接调用手势的处理函数
- public boolean onTouch(View v, MotionEvent event) {
- return mGestureDetector.onTouchEvent(event);
- }
查看GestureDetector类的onTouchEvent的源码就能知道,进入该函数后会进入case MotionEvent.ACTION_UP这个路径,从而调用onFling函数
如果需要设置activity切换效果,在startActivity(intent);之后添加
overridePendingTransition(android.R.anim.slide_in_left, android.R.anim.slide_out_right);即可,可修改相应参数,可参考http://www.iteye.com/topic/1116472
其他:
关于activity添加ScrollView后onFling不起作用,无法滑动问题见http://trinea.iteye.com/blog/1213815
- public class GestureDetector {
- // TODO: ViewConfiguration
- private int mBiggerTouchSlopSquare = 20 * 20;//touch事件最大超时时间的平方
- private int mTouchSlopSquare;//touch事件超时的时间平方
- private int mDoubleTapSlopSquare;//双击事件超时时间的平方
- private int mMinimumFlingVelocity;//最小滑动速率
- private int mMaximumFlingVelocity;//最大滑动速率
- private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();//长按超时
- private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();//单击超时
- private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();//双击超时
- // constants for Message.what used by GestureHandler below
- private static final int SHOW_PRESS = 1;//短按标志
- private static final int LONG_PRESS = 2;//长按标志
- private static final int TAP = 3;//轻击标志
- private final Handler mHandler;// Handler
- private final OnGestureListener mListener;// 普通手势监听器
- private OnDoubleTapListener mDoubleTapListener;// 双击或快速单击监听器
- private boolean mStillDown;//是否按下就不动了
- private boolean mInLongPress;//是否在长按过程中
- private boolean mAlwaysInTapRegion;//是否一直点击同一个位置
- private boolean mAlwaysInBiggerTapRegion;//是否在更大的范围内点击
- private MotionEvent mCurrentDownEvent;// 这次手势按下的事件
- private MotionEvent mPreviousUpEvent;// 上次手势抬起的事件
- //如果用户仍然处于第二次点击的过程(按下,滑动,抬起),就为true。只能为真 如果有双击事件的监听器就只能为true
- private boolean mIsDoubleTapping;
- private float mLastMotionY;//最后一次动作的Y坐标
- private float mLastMotionX;//最后一次动作的X坐标
- private boolean mIsLongpressEnabled;//长按事件是否启用
- /**
- * 如果我们试用的API的版本级别>=Froyo,或者开发人员去显示的设置它,就为true。
- * 如果为true,输入事件>1个触摸点,将会被忽略。
- * 那么我们就能更好并排着的检测多点触控手势。
- */
- private boolean mIgnoreMultitouch;//是否支持多点touch事件
- /**
- * 解决滑动持续时候的速度
- */
- private VelocityTracker mVelocityTracker;// 追踪触摸事件的速率
- /**
- *处理某些指定的手势
- */
- private class GestureHandler extends Handler {
- GestureHandler() {
- super();
- }
- GestureHandler(Handler handler) {
- super(handler.getLooper());
- }
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case SHOW_PRESS:
- mListener.onShowPress(mCurrentDownEvent);
- break;
- case LONG_PRESS:
- dispatchLongPress();
- break;
- case TAP:
- // If the user's finger is still down, do not count it as a tap
- if (mDoubleTapListener != null && !mStillDown) {
- mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
- }
- break;
- default:
- throw new RuntimeException("Unknown message " + msg); // never
- }
- }
- }
- /**
- * 在一个非UI线程中创建一个GestureDetector
- * 已经过时了,用下面的构造方法代替
- * public GestureDetector(Context context, OnGestureListener listener, Handler handler)
- */
- @Deprecated
- public GestureDetector(OnGestureListener listener, Handler handler) {
- this(null, listener, handler);
- }
- /**
- * 在一个非UI线程中创建一个GestureDetector
- * 已经过时了,用下面的构造方法代替
- * public GestureDetector(Context context, OnGestureListener listener, Handler handler)
- */
- @Deprecated
- public GestureDetector(OnGestureListener listener) {
- this(null, listener, null);
- }
- /**
- * 在UI线程中创建一个GestureDetector
- */
- public GestureDetector(Context context, OnGestureListener listener) {
- this(context, listener, null);
- }
- /**
- * 在UI线程中创建一个GestureDetector
- */
- public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
- this(context, listener, handler, context != null
- && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO);
- }
- /**
- * 在UI线程中创建一个GestureDetector
- * 不管你用的是哪个方法产生实例,都会调用这个构造器
- */
- public GestureDetector(Context context, OnGestureListener listener, Handler handler,
- boolean ignoreMultitouch) {
- if (handler != null) {
- mHandler = new GestureHandler(handler);
- } else {
- mHandler = new GestureHandler();
- }
- mListener = listener;
- //如果,这个listener只是实现了OnDoubleTapListener接口,就调用setOnDoubleTapListener方法
- //,初始化mDoubleTapListener对象
- if (listener instanceof OnDoubleTapListener) {
- setOnDoubleTapListener((OnDoubleTapListener) listener);
- }
- init(context, ignoreMultitouch);
- }
- /**
- * 初始化信息
- */
- private void init(Context context, boolean ignoreMultitouch) {
- //如果没有手势检测类,将抛出异常
- if (mListener == null) {
- throw new NullPointerException("OnGestureListener must not be null");
- }
- mIsLongpressEnabled = true;//默认长按事件开启
- mIgnoreMultitouch = ignoreMultitouch;//对多点触摸的处理
- // Fallback to support pre-donuts releases
- int touchSlop, doubleTapSlop;//touch超时,双击超时
- //对于一些超时操作需要变量的定义,通俗点说,就是不同事件的转变时间的问题
- //比如 按住多长,变为longPress事件,双击事件之间的时间间隔
- if (context == null) {
- // noinspection deprecation
- touchSlop = ViewConfiguration.getTouchSlop();
- doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
- // noinspection deprecation
- mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
- mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
- } else {
- final ViewConfiguration configuration = ViewConfiguration.get(context);
- touchSlop = configuration.getScaledTouchSlop();
- doubleTapSlop = configuration.getScaledDoubleTapSlop();
- mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
- mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
- }
- mTouchSlopSquare = touchSlop * touchSlop;
- mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
- }
- /**
- * 设置回调双击事件和解释手势行为的监听器
- */
- public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
- mDoubleTapListener = onDoubleTapListener;
- }
- /**
- * 如果你设置true的话就是开启了长按键,当你长时间触屏不动就能得到 onLongPress 手势,
- * 如果设置false 那么你长时间触屏不移动也得不到这个手势的支持
- * 默认设置为true
- */
- public void setIsLongpressEnabled(boolean isLongpressEnabled) {
- mIsLongpressEnabled = isLongpressEnabled;
- }
- /**
- * 返回长按事件传播的true或者false
- */
- public boolean isLongpressEnabled() {
- return mIsLongpressEnabled;
- }
- /**
- * 分析给出的事件,如何适用的话,就会去触发我们所提供的OnGestureListener中的
- * 回调方法
- */
- public boolean onTouchEvent(MotionEvent ev) {
- final int action = ev.getAction();//事件的类型
- final float y = ev.getY();//事件的x坐标
- final float x = ev.getX();//事件的y坐标
- //初始化速率追踪者,并将事件添加进去
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(ev);
- boolean handled = false;//是否要发消失
- switch (action & MotionEvent.ACTION_MASK) {//判断事件的类型
- case MotionEvent.ACTION_POINTER_DOWN://非主触点按下的时候
- if (mIgnoreMultitouch) {//如果忽略多点触摸,那么就调用cancel()方法
- // Multitouch event - abort.
- cancel();
- }
- break;
- case MotionEvent.ACTION_POINTER_UP://非主触点抬起的时候
- // Ending a multitouch gesture and going back to 1 finger
- if (mIgnoreMultitouch && ev.getPointerCount() == 2) {
- //获得触点的id
- int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1
- : 0;
- mLastMotionX = ev.getX(index);//得到x,作为上一次的事件x坐标保存
- mLastMotionY = ev.getY(index);//得到y,作为上一次的事件y坐标保存
- mVelocityTracker.recycle();//回收mVelocityTracker
- mVelocityTracker = VelocityTracker.obtain();//重现得到
- }
- break;
- case MotionEvent.ACTION_DOWN://按下的事件
- if (mDoubleTapListener != null) {
- boolean hadTapMessage = mHandler.hasMessages(TAP);
- if (hadTapMessage)
- mHandler.removeMessages(TAP);
- if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage
- && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
- // This is a second tap
- mIsDoubleTapping = true;
- // Give a callback with the first tap of the double-tap
- handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
- // Give a callback with down event of the double-tap
- handled |= mDoubleTapListener.onDoubleTapEvent(ev);
- } else {
- // This is a first tap
- mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
- }
- }
- mLastMotionX = x;
- mLastMotionY = y;
- if (mCurrentDownEvent != null) {
- mCurrentDownEvent.recycle();
- }
- mCurrentDownEvent = MotionEvent.obtain(ev);
- mAlwaysInTapRegion = true;
- mAlwaysInBiggerTapRegion = true;
- mStillDown = true;
- mInLongPress = false;
- if (mIsLongpressEnabled) {
- mHandler.removeMessages(LONG_PRESS);
- mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
- + TAP_TIMEOUT + LONGPRESS_TIMEOUT);
- }
- mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime()
- + TAP_TIMEOUT);
- handled |= mListener.onDown(ev);
- break;
- case MotionEvent.ACTION_MOVE:
- if (mInLongPress || (mIgnoreMultitouch && ev.getPointerCount() > 1)) {
- break;
- }
- final float scrollX = mLastMotionX - x;
- final float scrollY = mLastMotionY - y;
- if (mIsDoubleTapping) {
- // Give the move events of the double-tap
- handled |= mDoubleTapListener.onDoubleTapEvent(ev);
- } else if (mAlwaysInTapRegion) {
- final int deltaX = (int) (x - mCurrentDownEvent.getX());
- final int deltaY = (int) (y - mCurrentDownEvent.getY());
- int distance = (deltaX * deltaX) + (deltaY * deltaY);
- if (distance > mTouchSlopSquare) {
- handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
- mLastMotionX = x;
- mLastMotionY = y;
- mAlwaysInTapRegion = fal6se;
- mHandler.removeMessages(TAP);
- mHandler.removeMessages(SHOW_PRESS);
- mHandler.removeMessages(LONG_PRESS);
- }
- if (distance > mBiggerTouchSlopSquare) {
- mAlwaysInBiggerTapRegion = false;
- }
- } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
- handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
- mLastMotionX = x;
- mLastMotionY = y;
- }
- break;
- case MotionEvent.ACTION_UP:
- mStillDown = false;
- MotionEvent currentUpEvent = MotionEvent.obtain(ev);
- if (mIsDoubleTapping) {
- // Finally, give the up event of the double-tap
- handled |= mDoubleTapListener.onDoubleTapEvent(ev);
- } else if (mInLongPress) {
- mHandler.removeMessages(TAP);
- mInLongPress = false;
- } else if (mAlwaysInTapRegion) {
- handled = mListener.onSingleTapUp(ev);
- } else {
- // A fling must travel the minimum tap distance
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
- final float velocityY = velocityTracker.getYVelocity();
- final float velocityX = velocityTracker.getXVelocity();
- if ((Math.abs(velocityY) > mMinimumFlingVelocity)
- || (Math.abs(velocityX) > mMinimumFlingVelocity)) {
- handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
- }
- }
- if (mPreviousUpEvent != null) {
- mPreviousUpEvent.recycle();
- }
- // Hold the event we obtained above - listeners may have changed the
- // original.
- mPreviousUpEvent = currentUpEvent;
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- mIsDoubleTapping = false;
- mHandler.removeMessages(SHOW_PRESS);
- mHandler.removeMessages(LONG_PRESS);
- break;
- case MotionEvent.ACTION_CANCEL:
- cancel();
- }
- return handled;
- }
- /**
- * 私有方法,取消的方法,移动消息队列中的消息,释放内存
- */
- private void cancel() {
- mHandler.removeMessages(SHOW_PRESS);
- mHandler.removeMessages(LONG_PRESS);
- mHandler.removeMessages(TAP);
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- mIsDoubleTapping = false;
- mStillDown = false;
- if (mInLongPress) {
- mInLongPress = false;
- }
- }
- /**
- * 判断双击事件中,两次点击的位置关系。
- * 如果间隔很远,就不触发双击的事件
- */
- private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
- MotionEvent secondDown) {
- if (!mAlwaysInBiggerTapRegion) {
- return false;
- }
- if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) {
- return false;
- }
- int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
- int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
- return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
- }
- private void dispatchLongPress() {
- mHandler.removeMessages(TAP);
- mInLongPress = true;
- mListener.onLongPress(mCurrentDownEvent);
- }
- }