关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。
未经允许不得转载
目录
- 一、导读
- 二、概览
- 三、使用
- 四、 如何实现触摸滚动
- 五、 如何拖动对象 、缩放对象
- 基本尺寸示例
- 六、 推荐阅读
一、导读
我们继续总结学习基础知识,温故知新。
本文主要描述了手势监听相关的知识。
二、概览
Android事件处理机制是基于Listener实现的,比如触摸屏相关的事件,就是通过onTouchListener实现,
通过MotionEvent的getAction()方法来获取Touch事件的类型,包括 ACTION_DOWN(按下触摸屏),
ACTION_MOVE(按下触摸屏后移动受力点), ACTION_UP(松开触摸屏)和ACTION_CANCEL(不会由用户直接触发)。
借助对于用户不同操作的判断,结合getRawX()、getRawY()、getX()和getY()等方法来获取坐标后,我们可以实现诸如拖动某一个按钮,拖动滚动条等功能。
但是如果是更复杂的一些动画,比如用户在屏幕上的手势,这就需要另外一个接口了,GestureDetector.OnGestureListener接口。
Android 提供了GestureDetector(手势识别)类,通过这个类我们可以识别很多的手势,主要是通过他的onTouchEvent(event)方法完成了不同手势的识别。
三、使用
我们先来看下基本的使用,方法的注释我们都写代码里面
private class MyGesturelistener implements GestureDetector.OnGestureListener{/*** 用户按下屏幕触发* @param e* @return*/public boolean onDown(MotionEvent e) { return false; } /*** 如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,那么onShowPress就会执行 * 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发 * 注意和onDown()的区别,强调的是没有松开或者拖动的状态* @param e*/public void onShowPress(MotionEvent e) { } /*** 长按触摸屏,超过一定时长,就会触发这个事件 触发顺序: onDown->onShowPress->onLongPress * @param e*/public void onLongPress(MotionEvent e) { } /*** 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发 * 一次单独的轻击抬起操作,也就是轻击一下屏幕,立刻抬起来,才会有这个触发,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以也就不会触发这个事件 * 触发顺序: * 点击一下非常快的(不滑动)Touchup: * onDown->onSingleTapUp->onSingleTapConfirmed * 点击一下稍微慢点的(不滑动)Touchup: * onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed * @param e*/public boolean onSingleTapUp(MotionEvent e) { return false; } /*** 在屏幕上拖动事件 ,用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发* @param e1* onDown------》onScroll----》onScroll------》onFiling*/public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } /*** 滑屏,用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发* @param e1 第1个ACTION_DOWN MotionEvent* @param e2 最后一个ACTION_MOVE MotionEvent* @param velocityX X轴上的移动速度,像素/秒 * @param velocityY Y轴上的移动速度,像素/秒* onDown-----》onScroll----》onScroll----》onScroll----》………----->onFling */public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; }
}
这里有两种不同类型的滚动,也是我们平常用的比较多的方法,onScroll()、onFling()
-
拖动 是用户在触摸屏上拖动手指时发生的滚动类型。简单的拖动通常通过重写来 onScroll()
实现GestureDetector.OnGestureListener。有关拖动的更多信息,请参阅拖动和缩放。 -
拖放 是用户快速拖动并抬起手指时发生的滚动类型。用户抬起手指后,他们通常希望继续滚动(移动视口),
但要放慢速度,直到视口停止移动。拖放可以通过覆盖 onFling() 并GestureDetector.OnGestureListener使用滚动对象来实现。
还有一个双击相关的接口,我们一起来看看代码
private class MySimpleGesture extends SimpleOnGestureListener { /*** 双击的第二下Touch down时触发* @param e* @return*/ public boolean onDoubleTap(MotionEvent e) { return super.onDoubleTap(e); } /*** 双击的第二下Touch down和up都会触发,可用e.getAction()区分 * @param e* @return*/ public boolean onDoubleTapEvent(MotionEvent e) { return super.onDoubleTapEvent(e); } /*** Touch down时触发 * @param e* @return*/ public boolean onDown(MotionEvent e) { return super.onDown(e); } /*** Touch了滑动一点距离后,up时触发* @param e1* @return*/ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return super.onFling(e1, e2, velocityX, velocityY); } // Touch了不移动一直Touch down时触发 public void onLongPress(MotionEvent e) { super.onLongPress(e); } // Touch了滑动时触发 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return super.onScroll(e1, e2, distanceX, distanceY); } /** * Touch了还没有滑动时触发 * (1)onDown只要Touch Down一定立刻触发 * (2)Touch Down后过一会没有滑动先触发onShowPress再触发onLongPress * So: Touch Down后一直不滑动,onDown -> onShowPress -> onLongPress这个顺序触发。 */ public void onShowPress(MotionEvent e) { super.onShowPress(e); } /** * 两个函数都是在Touch Down后又没有滑动(onScroll),又没有长按(onLongPress),然后Touch Up时触发 * 点击一下非常快的(不滑动)Touch Up: onDown->onSingleTapUp->onSingleTapConfirmed * 点击一下稍微慢点的(不滑动)Touch Up: onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed */ public boolean onSingleTapConfirmed(MotionEvent e) { return super.onSingleTapConfirmed(e); } public boolean onSingleTapUp(MotionEvent e) { return super.onSingleTapUp(e); } }
下面可以看到一个片段,说明了调整尺寸所涉及的基本成分。
四、 如何实现触摸滚动
使用并重写的GestureDetector方法。用于跟踪拖放手势。如果用户在拖放手势后达到内容限制,应用程序会显示“发光”效果
// The current viewport. This rectangle represents the currently visible// chart domain and range. The viewport is the part of the app that the// user manipulates via touch gestures.private RectF mCurrentViewport =new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);// The current destination rectangle (in pixel coordinates) into which the// chart data should be drawn.private Rect mContentRect;private OverScroller mScroller;private RectF mScrollerStartViewport;...private final GestureDetector.SimpleOnGestureListener mGestureListener= new GestureDetector.SimpleOnGestureListener() {@Overridepublic boolean onDown(MotionEvent e) {// Initiates the decay phase of any active edge effects.releaseEdgeEffects();mScrollerStartViewport.set(mCurrentViewport);// Aborts any active scroll animations and invalidates.mScroller.forceFinished(true);ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);return true;}...@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2,float velocityX, float velocityY) {fling((int) -velocityX, (int) -velocityY);return true;}};private void fling(int velocityX, int velocityY) {// Initiates the decay phase of any active edge effects.releaseEdgeEffects();// Flings use math in pixels (as opposed to math based on the viewport).Point surfaceSize = computeScrollSurfaceSize();mScrollerStartViewport.set(mCurrentViewport);int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left -AXIS_X_MIN) / (AXIS_X_MAX - AXIS_X_MIN));int startY = (int) (surfaceSize.y * (AXIS_Y_MAX -mScrollerStartViewport.bottom) / (AXIS_Y_MAX - AXIS_Y_MIN));// Before flinging, aborts the current animation.mScroller.forceFinished(true);// Begins the animationmScroller.fling(// Current scroll positionstartX,startY,velocityX,velocityY,/** Minimum and maximum scroll positions. The minimum scroll* position is generally zero and the maximum scroll position* is generally the content size less the screen size. So if the* content width is 1000 pixels and the screen width is 200* pixels, the maximum scroll offset should be 800 pixels.*/0, surfaceSize.x - mContentRect.width(),0, surfaceSize.y - mContentRect.height(),// The edges of the content. This comes into play when using// the EdgeEffect class to draw "glow" overlays.mContentRect.width() / 2,mContentRect.height() / 2);// Invalidates to trigger computeScroll()ViewCompat.postInvalidateOnAnimation(this);}
当onFling() 调用时postInvalidateOnAnimation(),它会触发computeScroll()更新 x 和 y 值。通常,这是在子视图使用滚动对象对滚动进行动画处理时完成的
我们来看一段缩放代码
// Custom object that is functionally similar to ScrollerZoomer mZoomer;private PointF mZoomFocalPoint = new PointF();...// If a zoom is in progress (either programmatically or via double// touch), performs the zoom.if (mZoomer.computeZoom()) {float newWidth = (1f - mZoomer.getCurrZoom()) *mScrollerStartViewport.width();float newHeight = (1f - mZoomer.getCurrZoom()) *mScrollerStartViewport.height();float pointWithinViewportX = (mZoomFocalPoint.x -mScrollerStartViewport.left)/ mScrollerStartViewport.width();float pointWithinViewportY = (mZoomFocalPoint.y -mScrollerStartViewport.top)/ mScrollerStartViewport.height();mCurrentViewport.set(mZoomFocalPoint.x - newWidth * pointWithinViewportX,mZoomFocalPoint.y - newHeight * pointWithinViewportY,mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY));constrainViewport();needsInvalidate = true;}if (needsInvalidate) {ViewCompat.postInvalidateOnAnimation(this);}
computeScrollSurfaceSize()这是上一个代码片段中调用的方法。计算可滚动表面的当前大小(以像素为单位)。
例如,如果整个图表区域可见,则这是 的当前大小mContentRect。如果图形在两个方向上放大 200%,则显示的尺寸将是水平和垂直尺寸的两倍
有关使用滚动的另一个示例,请参阅该类的源代码ViewPager。它会滚动以响应拖放手势,并使用滚动来实现“适合页面”动画。
private ScaleGestureDetector mScaleDetector;private float mScaleFactor = 1.f;public MyCustomView(Context mContext){...// View code goes here...mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());}@Overridepublic boolean onTouchEvent(MotionEvent ev) {// Let the ScaleGestureDetector inspect all events.mScaleDetector.onTouchEvent(ev);return true;}@Overridepublic void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.save();canvas.scale(mScaleFactor, mScaleFactor);...// onDraw() code goes here...canvas.restore();}private class ScaleListenerextends ScaleGestureDetector.SimpleOnScaleGestureListener {@Overridepublic boolean onScale(ScaleGestureDetector detector) {mScaleFactor *= detector.getScaleFactor();// Don't let the object get too small or too large.mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));invalidate();return true;}}
五、 如何拖动对象 、缩放对象
以下代码片段允许用户在屏幕上拖动对象。记录活动指针的起始位置,计算指针移动的距离,并将对象移动到新位置。正确处理附加指针的可能性
// The ‘active pointer’ is the one currently moving our object.private int mActivePointerId = INVALID_POINTER_ID;@Overridepublic boolean onTouchEvent(MotionEvent ev) {// Let the ScaleGestureDetector inspect all events.mScaleDetector.onTouchEvent(ev);final int action = MotionEventCompat.getActionMasked(ev);switch (action) {case MotionEvent.ACTION_DOWN: {final int pointerIndex = MotionEventCompat.getActionIndex(ev);final float x = MotionEventCompat.getX(ev, pointerIndex);final float y = MotionEventCompat.getY(ev, pointerIndex);// Remember where we started (for dragging)mLastTouchX = x;mLastTouchY = y;// Save the ID of this pointer (for dragging)mActivePointerId = MotionEventCompat.getPointerId(ev, 0);break;}case MotionEvent.ACTION_MOVE: {// Find the index of the active pointer and fetch its positionfinal int pointerIndex =MotionEventCompat.findPointerIndex(ev, mActivePointerId);final float x = MotionEventCompat.getX(ev, pointerIndex);final float y = MotionEventCompat.getY(ev, pointerIndex);// Calculate the distance movedfinal float dx = x - mLastTouchX;final float dy = y - mLastTouchY;mPosX += dx;mPosY += dy;invalidate();// Remember this touch position for the next move eventmLastTouchX = x;mLastTouchY = y;break;}case MotionEvent.ACTION_UP: {mActivePointerId = INVALID_POINTER_ID;break;}case MotionEvent.ACTION_CANCEL: {mActivePointerId = INVALID_POINTER_ID;break;}case MotionEvent.ACTION_POINTER_UP: {final int pointerIndex = MotionEventCompat.getActionIndex(ev);final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);if (pointerId == mActivePointerId) {// This was our active pointer going up. Choose a new// active pointer and adjust accordingly.final int newPointerIndex = pointerIndex == 0 ? 1 : 0;mLastTouchX = MotionEventCompat.getX(ev, newPointerIndex);mLastTouchY = MotionEventCompat.getY(ev, newPointerIndex);mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);}break;}}return true;}
基本尺寸示例
下面可以看到一个片段,说明了调整尺寸所涉及的基本成分
private ScaleGestureDetector mScaleDetector;private float mScaleFactor = 1.f;public MyCustomView(Context mContext){...// View code goes here...mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());}@Overridepublic boolean onTouchEvent(MotionEvent ev) {// Let the ScaleGestureDetector inspect all events.mScaleDetector.onTouchEvent(ev);return true;}@Overridepublic void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.save();canvas.scale(mScaleFactor, mScaleFactor);...// onDraw() code goes here...canvas.restore();}private class ScaleListenerextends ScaleGestureDetector.SimpleOnScaleGestureListener {@Overridepublic boolean onScale(ScaleGestureDetector detector) {mScaleFactor *= detector.getScaleFactor();// Don't let the object get too small or too large.mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));invalidate();return true;}}
还有更多的复杂手势,建议深度学习源码,本文只是一个记录。
六、 推荐阅读
Java 专栏
SQL 专栏
数据结构与算法
Android学习专栏
未经允许不得转载