如何检测左/右和上/下之间的滑动方向
我的问题:如何检测用户上/下/左/右移动手指(以及如何知道手指移动到哪个方向)?
我的情况:我想在上下移动手指时改变应用程序的亮度(向上=较亮,向下=较暗),而且我想根据左右滑动在活动和/或视图之间切换。
你只需要扩展SimpleOnGestureListener类,
在你的class级里宣布这个,
private static final int SWIPE_MIN_DISTANCE = 120; private static final int SWIPE_MAX_OFF_PATH = 250; private static final int SWIPE_THRESHOLD_VELOCITY = 200;
作为横向轻扫的例子,你可以看到下面的代码,
class MyGestureDetector extends SimpleOnGestureListener { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { try { if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH){ return false; } // right to left swipe if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { onLeftSwipe(); } // left to right swipe else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { onRightSwipe(); } } catch (Exception e) { } return false; } }
你可以做类似的垂直刷卡的目的。
我写了一个简单的类:它有很好的文档,所以我不会在这里解释
public class OnSwipeListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // Grab two events located on the plane at e1=(x1, y1) and e2=(x2, y2) // Let e1 be the initial event // e2 can be located at 4 different positions, consider the following diagram // (Assume that lines are separated by 90 degrees.) // // // \ A / // \ / // D e1 B // / \ // / C \ // // So if (x2,y2) falls in region: // A => it's an UP swipe // B => it's a RIGHT swipe // C => it's a DOWN swipe // D => it's a LEFT swipe // float x1 = e1.getX(); float y1 = e1.getY(); float x2 = e2.getX(); float y2 = e2.getY(); Direction direction = getDirection(x1,y1,x2,y2); return onSwipe(direction); } /** Override this method. The Direction enum will tell you how the user swiped. */ public boolean onSwipe(Direction direction){ return false; } /** * Given two points in the plane p1=(x1, x2) and p2=(y1, y1), this method * returns the direction that an arrow pointing from p1 to p2 would have. * @param x1 the x position of the first point * @param y1 the y position of the first point * @param x2 the x position of the second point * @param y2 the y position of the second point * @return the direction */ public Direction getDirection(float x1, float y1, float x2, float y2){ double angle = getAngle(x1, y1, x2, y2); return Direction.get(angle); } /** * * Finds the angle between two points in the plane (x1,y1) and (x2, y2) * The angle is measured with 0/360 being the X-axis to the right, angles * increase counter clockwise. * * @param x1 the x position of the first point * @param y1 the y position of the first point * @param x2 the x position of the second point * @param y2 the y position of the second point * @return the angle between two points */ public double getAngle(float x1, float y1, float x2, float y2) { double rad = Math.atan2(y1-y2,x2-x1) + Math.PI; return (rad*180/Math.PI + 180)%360; } public enum Direction{ up, down, left, right; /** * Returns a direction given an angle. * Directions are defined as follows: * * Up: [45, 135] * Right: [0,45] and [315, 360] * Down: [225, 315] * Left: [135, 225] * * @param angle an angle from 0 to 360 - e * @return the direction of an angle */ public static Direction get(double angle){ if(inRange(angle, 45, 135)){ return Direction.up; } else if(inRange(angle, 0,45) || inRange(angle, 315, 360)){ return Direction.right; } else if(inRange(angle, 225, 315)){ return Direction.down; } else{ return Direction.left; } } /** * @param angle an angle * @param init the initial bound * @param end the final bound * @return returns true if the given angle is in the interval [init, end). */ private static boolean inRange(double angle, float init, float end){ return (angle >= init) && (angle < end); } } }
要只使用扩展OnSwipeListener
并覆盖onSwipe
方法
费尔南多尔的回答是完美的,我正在写这个答案,关于如何使用它与Activity
和Fragment
因为许多人正在寻找它。
public class MyActivity extends Activity implements View.OnTouchListener{ private RelativeLayout someLayout; //take any layout on which you want your gesture listener; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); gestureDetector=new GestureDetector(this,new OnSwipeListener(){ @Override public boolean onSwipe(Direction direction) { if (direction==Direction.up){ //do your stuff Log.d(TAG, "onSwipe: up"); } if (direction==Direction.down){ //do your stuff Log.d(TAG, "onSwipe: down"); } return true; } }); someLayout.setOnTouchListener(this); } @Override public boolean onTouch(View v, MotionEvent event) { Log.d(TAG, "onTouch: "); gestureDetector.onTouchEvent(event); return true; } }
上面给出了fernandohur的用法示例 :
如果你想把OnSwipeListener应用到你的某个视图,那么:
无论这个视图在哪里 – 为该视图设置一个触摸监听器,如下所示:
myview.setOnTouchListener(this);
现在在你的Activity的OnCreate或你的Custom视图构造函数中执行以下操作:
// Global private GestureDetectorCompat detector; // In OnCreate or custome view constructor (which extends one of Android views) detector = new GestureDetectorCompat(context, onSwipeListener);
在同一个类中覆盖onTouch事件,如下所示:
@Override public boolean onTouch(View view, MotionEvent motionEvent) { return detector.onTouchEvent(motionEvent); }
并且在同一个类中也有这个监听器对象:
OnSwipeListener onSwipeListener = new OnSwipeListener() { @Override public boolean onSwipe(Direction direction) { // Possible implementation if(direction == Direction.left|| direction == Direction.right) { // Do something COOL like animation or whatever you want // Refer to your view if needed using a global reference return true; } else if(direction == Direction.up|| direction == Direction.down) { // Do something COOL like animation or whatever you want // Refer to your view if needed using a global reference return true; } return super.onSwipe(direction); }
};
我在这个库中的bitbucket上有一个开源手势库,是一个'HGFling'类。 这演示了如何检测投掷方向。 您基本上logging了手势向下事件的XY和向上事件的XY,并将它们按比例分开,然后使用简单的乘法来检测animation的结束。 您可以从以下url下载图书馆: https : //bitbucket.org/warwick/hacergestov1 。 它是开源的。
这里是上述类的代码片段,它完全testing并完美地工作。
public HGResult getUp(MotionEvent event) { if(disableGestureDynamically == true) { return hgResult; } if(disableGestureInternally == true) { return hgResult; } //Check flingTimeThreashold if(System.currentTimeMillis() < gestureStartTime + flingTimeThreashold) { hgResult.setTwoFingerDistance(hgResult.getTwoFingerDistance(firstTouchX, firstTouchY, event.getRawX(), event.getRawY())); //Check flingDistanceThreashold if(hgResult.getTwoFingerDistance() > flingDistanceThreashold) { paddingL = imageView.getPaddingLeft(); paddingT = imageView.getPaddingTop(); paddingR = imageView.getPaddingRight(); paddingB = imageView.getPaddingBottom(); leftRelativeToPadding = imageView.getLeft() + paddingL; topRelativeToPadding = imageView.getTop() + paddingT; rightRelativeToPadding = imageView.getMeasuredWidth() - paddingR; bottomRelativeToPadding = imageView.getMeasuredHeight() - paddingB; widthToHEightFlingRatio = Math.abs(firstTouchY - event.getRawY()) / Math.abs(firstTouchX - event.getRawX()); float animationStartX = matrixVals[Matrix.MTRANS_X]; float animationEndX = 0; float animationStartY = matrixVals[Matrix.MTRANS_Y]; float animationEndY = 0; if(dynamicFlingDistance == 0) {//This block is for default fling behaviour flings to the nearest edge. if(firstTouchX < event.getRawX()) {//Fling to right if(firstTouchY < event.getRawY()) {//Fling right & down if((point.x - leftRelativeToPadding - imageView.getLeft()) * widthToHEightFlingRatio < point.x) { //Right edge is closest animationEndX = imageView.getMeasuredWidth() - paddingL; animationEndY = (int) ((imageView.getBottom() - topRelativeToPadding) * widthToHEightFlingRatio); } else { //Bottom edge is closest animationEndX = (int) ((imageView.getRight() - leftRelativeToPadding) / widthToHEightFlingRatio); animationEndY = imageView.getMeasuredHeight() - paddingT; }//End if((point.x - leftRelativeToPadding - imageView.getLeft()) * widthToHEightFlingRatio < point.x) } else /*if(firstTouchY > releaseY)*/ {//Fling right & up if((point.x - leftRelativeToPadding - imageView.getLeft()) * widthToHEightFlingRatio < point.x) { //Right edge is closest animationEndX = imageView.getMeasuredWidth() - paddingL; animationEndY = (int) -((imageView.getBottom() - topRelativeToPadding) * widthToHEightFlingRatio); } else { //Top edge is closest animationEndX = (int) ((imageView.getRight() - leftRelativeToPadding) / widthToHEightFlingRatio); animationEndY = -(imageView.getMeasuredHeight() - paddingT); }//End if((point.x - leftRelativeToPadding - imageView.getLeft()) * widthToHEightFlingRatio < point.x) }//End if(firstTouchY < event.getRawY()) } else /*if(firstTouchX > releaseX)*/ {//Fling to left if(firstTouchY < event.getRawY()) {//Fling left down if((point.x - leftRelativeToPadding - imageView.getLeft()) * widthToHEightFlingRatio < point.x) { //Left edge is closest animationEndX = -(imageView.getMeasuredWidth() - paddingL); animationEndY = (int) ((imageView.getBottom() - topRelativeToPadding) * widthToHEightFlingRatio); } else { //Bottom edge is closest animationEndX = (int) - ((imageView.getRight() - leftRelativeToPadding) / widthToHEightFlingRatio); animationEndY = imageView.getMeasuredHeight() - paddingT; }//End if((point.x - leftRelativeToPadding - imageView.getLeft()) * widthToHEightFlingRatio < point.x) } else /*if(firstTouchY > releaseY)*/ {//Fling left up if((point.x - leftRelativeToPadding - imageView.getLeft()) * widthToHEightFlingRatio < point.x) { //Left edge is closest animationEndX = -(imageView.getMeasuredWidth() - paddingL); animationEndY = (int) -((imageView.getBottom() - topRelativeToPadding) * widthToHEightFlingRatio); } else { //Top edge is closest animationEndX = (int) - ((imageView.getRight() - leftRelativeToPadding) / widthToHEightFlingRatio); animationEndY = -(imageView.getMeasuredHeight() - paddingT); }//End if((point.x - leftRelativeToPadding - imageView.getLeft()) * widthToHEightFlingRatio < point.x) }//End if(firstTouchY < event.getRawY()) }//End if(firstTouchX < event.getRawX()) translateAnimation = new TranslateAnimation(animationStartX, animationEndX, animationStartY, animationEndY); imageView.setAnimation(translateAnimation); imageView.getAnimation().setDuration(flingAnimationTime); translateAnimation.setAnimationListener(animationListener); imageView.startAnimation(translateAnimation); if(bounceBack == false) { final float animationEndXFinal = animationEndX; final float animationEndYFinal = animationEndY; new Handler().postDelayed( new Runnable() { @Override public void run() { matrixVals[Matrix.MTRANS_X] = animationEndXFinal; matrixVals[Matrix.MTRANS_Y] = animationEndYFinal; matrix.setValues(matrixVals); imageView.setImageMatrix(matrix); imageView.invalidate(); imageView.requestLayout(); } }, flingAnimationTime); }//End if(bounceBack == false) } else /*if(dynamicFlingDistance != 0)*/ {//condition that is called when setFlingDistance(int dynamicFlingDistance) method sets a non zero value triggerFling(dynamicFlingDistance, event.getRawX() - firstTouchX, event.getRawY() - firstTouchY); }//End if(dynamicFlingDistance == 0) }//End if(hgResult.getTwoFingerDistance() > flingDistanceThreashold) }//End if(System.currentTimeMillis() < gestureStartTime + flingTimeThreashold) hgResult.setFirstTouchX(event.getRawX()); hgResult.setFirstTouchY(event.getRawY()); hgResult.setMoveDistanceX(event.getRawX() - firstTouchX); hgResult.setMoveDistanceY(event.getRawY() - firstTouchY); return hgResult; }//End HGResult getUp(MotionEvent event)
我解决了这个问题:
viewPager.setOnTouchListener(new View.OnTouchListener() { float prevX = -1; @Override public boolean onTouch(View v, MotionEvent event) { if (prevX != -1) { if (event.getX() > prevX) { if (viewPager.getCurrentItem() == 0) { // Left to Right swipe } //Log.d("DEBUG", MotionEvent.ACTION_MOVE + ":" + event.getAction() + ":" + event.getActionMasked() + ":Left Swipe" + ":" + prevX + ":" + event.getX() + ":" + viewPager.getCurrentItem()); } else if (prevX > event.getX()) { // Right to left swipe //Log.d("DEBUG", MotionEvent.ACTION_MOVE + ":" + event.getAction() + ":" + event.getActionMasked() + ":Right Swipe" + ":" + prevX + ":" + event.getX() + ":" + viewPager.getCurrentItem()); } } if (event.getAction() == MotionEvent.ACTION_MOVE) { prevX = event.getX(); } else { prevX = -1; } return false; } });
对于这样一个简单的问题,可用的答案太复杂了。 我build议另一种方法(代码是as3,但你可以得到这个想法):
var touchDistance:Number = Point.distance(_moveTouchPoint, _startTouchPoint); if (touchDistance >= SWIPE_MIN_DISTANCE) { var xDiff:Number = _moveTouchPoint.x - _startTouchPoint.x; var yDiff:Number = _moveTouchPoint.y - _startTouchPoint.y; var yGreater:Boolean = Math.abs(yDiff) >= Math.abs(xDiff); if (yGreater) { // direction is up or down changePlayerDirectionTo(yDiff < 0 ? DIRECTION_UP : DIRECTION_DOWN); } else { // direction is left or right changePlayerDirectionTo(xDiff < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT) } }
在每种情况下,x或y的绝对值都会更大,这可以解决某些方向的设定。 从此,您可以依靠坐标来准确检测哪个方向。