关于作者: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)方法完成了不同手势的识别。
三、使用
我们先来看下基本的使用,方法的注释我们都写代码里面
java
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使用滚动对象来实现。
还有一个双击相关的接口,我们一起来看看代码
java
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方法。用于跟踪拖放手势。如果用户在拖放手势后达到内容限制,应用程序会显示"发光"效果
java
// 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() {
@Override
public 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;
}
...
@Override
public 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 animation
mScroller.fling(
// Current scroll position
startX,
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 值。通常,这是在子视图使用滚动对象对滚动进行动画处理时完成的
我们来看一段缩放代码
java
// Custom object that is functionally similar to Scroller
Zoomer 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。它会滚动以响应拖放手势,并使用滚动来实现"适合页面"动画。
java
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
public MyCustomView(Context mContext){
...
// View code goes here
...
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
return true;
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor);
...
// onDraw() code goes here
...
canvas.restore();
}
private class ScaleListener
extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public 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
// The 'active pointer' is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
@Override
public 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 position
final int pointerIndex =
MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, pointerIndex);
final float y = MotionEventCompat.getY(ev, pointerIndex);
// Calculate the distance moved
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
invalidate();
// Remember this touch position for the next move event
mLastTouchX = 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;
}
基本尺寸示例
下面可以看到一个片段,说明了调整尺寸所涉及的基本成分
java
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
public MyCustomView(Context mContext){
...
// View code goes here
...
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
return true;
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor);
...
// onDraw() code goes here
...
canvas.restore();
}
private class ScaleListener
extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public 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;
}
}
还有更多的复杂手势,建议深度学习源码,本文只是一个记录。
六、 推荐阅读
未经允许不得转载