网格布局上的翻转手势检测


1104

我想fling在我的Android应用程序中使用手势检测。

我所拥有的是一个GridLayout包含9 ImageViews的。可以在这里找到源:Romain Guys的Grid Layout

我带的那个文件来自Romain Guy的Photostream应用程序,只是稍作修改。

对于简单的单击情况,我只需要onClickListenerImageView我添加的每个对象设置activity,即可实现View.OnClickListener。实现可以识别的东西似乎更加复杂fling。我认为这是因为它可能跨越views

  • 如果我的活动实现了, OnGestureListener我不知道如何将其设置为我GridImage添加的视图的手势侦听器。

    public class SelectFilterActivity extends Activity implements
       View.OnClickListener, OnGestureListener { ...
    
  • 如果我的活动实现了, OnTouchListener那么我就没有 onFling办法override(它有两个事件作为参数,使我能够确定猛击是否值得关注)。

    public class SelectFilterActivity extends Activity implements
        View.OnClickListener, OnTouchListener { ...
    
  • 如果我进行自定义View,这样的GestureImageView扩展ImageView会导致我不知道如何fling从视图中告知活动。无论如何,我都尝试过这种方法,并且触摸屏幕时不会调用这些方法。

我真的只需要一个跨视图工作的具体示例。什么,什么时候以及如何附加这个listener?我还需要能够检测到单击。

// Gesture detection
mGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {

    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        int dx = (int) (e2.getX() - e1.getX());
        // don't accept the fling if it's too short
        // as it may conflict with a button push
        if (Math.abs(dx) > MAJOR_MOVE && Math.abs(velocityX) > Math.absvelocityY)) {
            if (velocityX > 0) {
                moveRight();
            } else {
                moveLeft();
            }
            return true;
        } else {
            return false;
        }
    }
});

是否可以在屏幕上方放置透明视图以捕获抓拍?

如果我不选择inflate从XML生成的子图像视图,是否可以将GestureDetectoras作为构造函数参数传递给ImageView我创建的新子类?

这是一个非常简单的活动,我正在尝试进行fling检测:SelectFilterActivity(从photostream改编而成)

我一直在寻找这些来源:

到目前为止,对我来说什么都没有奏效,我一直希望有一些指点。


如何解决这个问题呢?请回答stackoverflow.com/questions/60464912/...
Bishwash

Answers:


818

感谢Code Shogun,我适应了我的情况。

让您的活动OnClickListener照常执行:

public class SelectFilterActivity extends Activity implements OnClickListener {

  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;
  private GestureDetector gestureDetector;
  View.OnTouchListener gestureListener;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    /* ... */

    // Gesture detection
    gestureDetector = new GestureDetector(this, new MyGestureDetector());
    gestureListener = new View.OnTouchListener() {
      public boolean onTouch(View v, MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
      }
    };

  }

  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) {
          Toast.makeText(SelectFilterActivity.this, "Left Swipe", Toast.LENGTH_SHORT).show();
        } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
          Toast.makeText(SelectFilterActivity.this, "Right Swipe", Toast.LENGTH_SHORT).show();
        }
      } catch (Exception e) {
         // nothing
      }
      return false;
    }

    @Override
    public boolean onDown(MotionEvent e) {
      return true;
    }
  }
}

将手势侦听器附加到您添加到主布局中的所有视图;

// Do this for each view added to the grid
imageView.setOnClickListener(SelectFilterActivity.this); 
imageView.setOnTouchListener(gestureListener);

敬畏地观察onClick(View v)活动和onFling手势侦听器的覆盖方法被命中。

public void onClick(View v) {
  Filter f = (Filter) v.getTag();
  FilterFullscreenActivity.show(this, input, f);
}

后期的“跳”舞是可选的,但值得鼓励。


109
感谢您提供此代码!这非常有帮助。但是,在尝试使手势起作用时,我遇到了一个非常令人沮丧的陷阱。在我的SimpleOnGestureListener中,我必须重写onDown才能注册我的任何手势。它只能返回true,但必须定义。PS:我不知道它是我的api版本还是我的硬件,但是我在HTC Droid Eris上使用1.5。
Cdsboy,

我尝试了您的代码,无论我滑动还是单击(使用鼠标,因为我在仿真器中工作)都没有关系,我总是得到在onClick方法中定义的Toast,因此仿真器仅检测单击,而不会滑动。为什么会这样呢?
lomza 2011年

我尝试了这段代码,但没有成功。我将onClick侦听器应用于图库视图中的其中一个子视图时,仍然根本无法滚动
Jonathan

Iomza:您是否尝试过放置break语句并逐步执行代码?
IgorGanapolsky '12

使用内部类的荣誉!很干净的方法。
IgorGanapolsky '12

211

上面的答案之一提到处理不同的像素密度,但建议手动计算滑动参数。值得注意的是,您实际上可以使用ViewConfiguration类从系统获取经过缩放的合理值:

final ViewConfiguration vc = ViewConfiguration.get(getContext());
final int swipeMinDistance = vc.getScaledPagingTouchSlop();
final int swipeThresholdVelocity = vc.getScaledMinimumFlingVelocity();
final int swipeMaxOffPath = vc.getScaledTouchSlop();
// (there is also vc.getScaledMaximumFlingVelocity() one could check against)

我注意到使用这些值会使应用程序和系统其余部分之间的“感觉”更加一致。


11
我使用swipeMinDistance = vc.getScaledPagingTouchSlop()swipeMaxOffPath = vc.getScaledTouchSlop()
Thomas Ahle

8
getScaledTouchSlop尴尬地给我很少的抵消结果。例如,在540高屏幕上只有24像素,很难用手指将其保持在范围内。:S
WonderCsabo

148

我做了一点不同,并编写了一个额外的检测器类,该类实现了 View.onTouchListener

onCreate只需将其添加到最低的布局中,如下所示:

ActivitySwipeDetector activitySwipeDetector = new ActivitySwipeDetector(this);
lowestLayout = (RelativeLayout)this.findViewById(R.id.lowestLayout);
lowestLayout.setOnTouchListener(activitySwipeDetector);

其中id.lowestLayout是布局层次结构中视图最低的视图的id.xxx,而最低布局被声明为RelativeLayout

然后是实际的活动滑动检测器类:

public class ActivitySwipeDetector implements View.OnTouchListener {

static final String logTag = "ActivitySwipeDetector";
private Activity activity;
static final int MIN_DISTANCE = 100;
private float downX, downY, upX, upY;

public ActivitySwipeDetector(Activity activity){
    this.activity = activity;
}

public void onRightSwipe(){
    Log.i(logTag, "RightToLeftSwipe!");
    activity.doSomething();
}

public void onLeftSwipe(){
    Log.i(logTag, "LeftToRightSwipe!");
    activity.doSomething();
}

public void onDownSwipe(){
    Log.i(logTag, "onTopToBottomSwipe!");
    activity.doSomething();
}

public void onUpSwipe(){
    Log.i(logTag, "onBottomToTopSwipe!");
    activity.doSomething();
}

public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

       // swipe horizontal?
        if(Math.abs(deltaX) > Math.abs(deltaY))
        {
            if(Math.abs(deltaX) > MIN_DISTANCE){
                // left or right
                if(deltaX > 0) { this.onRightSwipe(); return true; }
                if(deltaX < 0) { this.onLeftSwipe(); return true; }
            }
            else {
                    Log.i(logTag, "Horizontal Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                    return false; // We don't consume the event
            }
        }
        // swipe vertical?
        else 
        {
            if(Math.abs(deltaY) > MIN_DISTANCE){
                // top or down
                if(deltaY < 0) { this.onDownSwipe(); return true; }
                if(deltaY > 0) { this.onUpSwipe(); return true; }
            }
            else {
                    Log.i(logTag, "Vertical Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                    return false; // We don't consume the event
            }
        }

            return true;
        }
    }
    return false;
}

}

对我来说真的很好!


1
实际上,这使我更容易应用手势功能,并减少了“更少”的布线:D感谢@Thomas
nemesisfixx 2011年

5
这看起来像一个整洁的实用工具类-但我认为您的四个on ... swipe()方法应该是接口
某处某人

2
这些回报不应该存在(“我们不消耗事件”行),不是吗?它禁用垂直滚动功能。
Marek Sebera 2011年

5
具体来说就是onTouch()方法。首先,如果增量X不够大,则返回时将不检查增量Y。结果是,永远不会检测到左右滑动。其次,如果它因找不到滑动而掉线,它也不应返回true。第三,它不应在行动失败时返回true。这样会阻止其他任何监听器(如onClick)正常工作。
Jeffrey Blattman 2012年

1
@Piotr,只要持有引用的对象与活动本身的作用域相同,就没有问题。当您在比活动更大的范围内保留对活动的引用时会发生此问题,例如来自静态成员。
杰弗里·布拉特曼

94

我对Thomas Fankhauser的解决方案进行了稍微的修改和修复

整个系统由两个文件SwipeInterfaceActivitySwipeDetector组成


SwipeInterface.java

import android.view.View;

public interface SwipeInterface {

    public void bottom2top(View v);

    public void left2right(View v);

    public void right2left(View v);

    public void top2bottom(View v);

}

探测器

import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class ActivitySwipeDetector implements View.OnTouchListener {

    static final String logTag = "ActivitySwipeDetector";
    private SwipeInterface activity;
    static final int MIN_DISTANCE = 100;
    private float downX, downY, upX, upY;

    public ActivitySwipeDetector(SwipeInterface activity){
        this.activity = activity;
    }

    public void onRightToLeftSwipe(View v){
        Log.i(logTag, "RightToLeftSwipe!");
        activity.right2left(v);
    }

    public void onLeftToRightSwipe(View v){
        Log.i(logTag, "LeftToRightSwipe!");
        activity.left2right(v);
    }

    public void onTopToBottomSwipe(View v){
        Log.i(logTag, "onTopToBottomSwipe!");
        activity.top2bottom(v);
    }

    public void onBottomToTopSwipe(View v){
        Log.i(logTag, "onBottomToTopSwipe!");
        activity.bottom2top(v);
    }

    public boolean onTouch(View v, MotionEvent event) {
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

            // swipe horizontal?
            if(Math.abs(deltaX) > MIN_DISTANCE){
                // left or right
                if(deltaX < 0) { this.onLeftToRightSwipe(v); return true; }
                if(deltaX > 0) { this.onRightToLeftSwipe(v); return true; }
            }
            else {
                Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
            }

            // swipe vertical?
            if(Math.abs(deltaY) > MIN_DISTANCE){
                // top or down
                if(deltaY < 0) { this.onTopToBottomSwipe(v); return true; }
                if(deltaY > 0) { this.onBottomToTopSwipe(v); return true; }
            }
            else {
                Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                v.performClick();
            }
        }
        }
        return false;
    }

}

它的用法是这样的:

ActivitySwipeDetector swipe = new ActivitySwipeDetector(this);
LinearLayout swipe_layout = (LinearLayout) findViewById(R.id.swipe_layout);
swipe_layout.setOnTouchListener(swipe);

在实现过程中,Activity您需要实现SwipeInterface中的方法,并且可以找出调用了哪个View Swipe事件

@Override
public void left2right(View v) {
    switch(v.getId()){
        case R.id.swipe_layout:
            // do your stuff here
        break;
    }       
}

我再次对其进行了轻微修改,请参阅v.performClick();,如果在同一视图上设置的话,该事件将不消耗OnClickListener事件
Marek Sebera'2

嗨,我是一个初学者,所以这个问题可能确实很明显或无关紧要,但请回答。您编写的部分用作:ActivitySwipeDetector swipe =新的ActivitySwipeDetector(this); 该语句将成为MainActivity的一部分,对吗?然后,“ this”将成为MainActivity的活动。而构造函数采用SwipeInterface的实例。请在这里帮助我。非常感谢。
Chocolava 2013年

@Chocolava提出新问题,在这样的问题上发表评论不是一个好地方。
Marek Sebera 2013年

@MarekSebera这不适用于ScrollView和ListView吗?如何处理?
Duc Tran

再次@silentbang,这不是问此类问题的地方。请创建新的问题线程。
Marek Sebera 2013年

65

上面的滑动手势检测器代码非常有用!但是,您可能希望通过使用以下相对值(REL_SWIPE)而非绝对值来使此解决方案密度不可知(SWIPE_)

DisplayMetrics dm = getResources().getDisplayMetrics();

int REL_SWIPE_MIN_DISTANCE = (int)(SWIPE_MIN_DISTANCE * dm.densityDpi / 160.0f);
int REL_SWIPE_MAX_OFF_PATH = (int)(SWIPE_MAX_OFF_PATH * dm.densityDpi / 160.0f);
int REL_SWIPE_THRESHOLD_VELOCITY = (int)(SWIPE_THRESHOLD_VELOCITY * dm.densityDpi / 160.0f);

8
+1提出来。请注意,DensityMetrics.densityDpi是在API 4中引入的。为了向后兼容API 1,请改用DensityMetrics.density。然后,这会将计算更改为SWIPE_MIN_DISTANCE * dm.density。
Thane Anthem

您从哪里获得160.0f的数字?
IgorGanapolsky 2011年

developer.android.com/guide/practices/screens_support.html密度无关的像素(dp)dp单位到屏幕像素的转换很简单:px = dp *(dpi / 160)
paiego 2012年

我一直在寻找这个。Internet上没有onFling()的示例具有此功能,这将导致UX不良。谢谢!
桑迪

160.0f是来自160 DPI的数据,它是DP(与密度无关的像素)所基于的标准密度。public static final int DENSITY_MEDIUM已添加到API级别4中密度屏幕的标准量化DPI。常数:160(0x000000a0)
paiego,2012年

35

我的解决方案版本由Thomas FankhauserMarek Sebera提出(不处理垂直滑动):

SwipeInterface.java

import android.view.View;

public interface SwipeInterface {

    public void onLeftToRight(View v);

    public void onRightToLeft(View v);
}

ActivitySwipeDetector.java

import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

public class ActivitySwipeDetector implements View.OnTouchListener {

    static final String logTag = "ActivitySwipeDetector";
    private SwipeInterface activity;
    private float downX, downY;
    private long timeDown;
    private final float MIN_DISTANCE;
    private final int VELOCITY;
    private final float MAX_OFF_PATH;

    public ActivitySwipeDetector(Context context, SwipeInterface activity){
        this.activity = activity;
        final ViewConfiguration vc = ViewConfiguration.get(context);
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        MIN_DISTANCE = vc.getScaledPagingTouchSlop() * dm.density;
        VELOCITY = vc.getScaledMinimumFlingVelocity();
        MAX_OFF_PATH = MIN_DISTANCE * 2;            
    }

    public void onRightToLeftSwipe(View v){
        Log.i(logTag, "RightToLeftSwipe!");
        activity.onRightToLeft(v);
    }

    public void onLeftToRightSwipe(View v){
        Log.i(logTag, "LeftToRightSwipe!");
        activity.onLeftToRight(v);
    }

    public boolean onTouch(View v, MotionEvent event) {
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            Log.d("onTouch", "ACTION_DOWN");
            timeDown = System.currentTimeMillis();
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            Log.d("onTouch", "ACTION_UP");
            long timeUp = System.currentTimeMillis();
            float upX = event.getX();
            float upY = event.getY();

            float deltaX = downX - upX;
            float absDeltaX = Math.abs(deltaX); 
            float deltaY = downY - upY;
            float absDeltaY = Math.abs(deltaY);

            long time = timeUp - timeDown;

            if (absDeltaY > MAX_OFF_PATH) {
                Log.i(logTag, String.format("absDeltaY=%.2f, MAX_OFF_PATH=%.2f", absDeltaY, MAX_OFF_PATH));
                return v.performClick();
            }

            final long M_SEC = 1000;
            if (absDeltaX > MIN_DISTANCE && absDeltaX > time * VELOCITY / M_SEC) {
                if(deltaX < 0) { this.onLeftToRightSwipe(v); return true; }
                if(deltaX > 0) { this.onRightToLeftSwipe(v); return true; }
            } else {
                Log.i(logTag, String.format("absDeltaX=%.2f, MIN_DISTANCE=%.2f, absDeltaX > MIN_DISTANCE=%b", absDeltaX, MIN_DISTANCE, (absDeltaX > MIN_DISTANCE)));
                Log.i(logTag, String.format("absDeltaX=%.2f, time=%d, VELOCITY=%d, time*VELOCITY/M_SEC=%d, absDeltaX > time * VELOCITY / M_SEC=%b", absDeltaX, time, VELOCITY, time * VELOCITY / M_SEC, (absDeltaX > time * VELOCITY / M_SEC)));
            }

        }
        }
        return false;
    }

}

有人可以告诉我该如何上课吗?ActivitySwipeDetector滑动=新的ActivitySwipeDetector(this); 显然是给了错误,因为没有这样的构造函数。我应该给ActivitySwipeDetector swipe = new ActivitySwipeDetector(this,null);
abdfahim

@AbdullahFahim ActivitySwipeDetector(this,YourActivity.this);
Anton Kashpor 2014年

25

这个问题有点陈旧,2011年7月Google发布了兼容性软件包修订版3),其中包括ViewPager适用于Android 1.6及更高版本的。GestureListener针对此问题发布的答案在Android上感觉不太优雅。如果您正在寻找用于在Android Gallery中切换照片或在新的Play Market应用中切换视图的代码,那么绝对是ViewPager

以下是更多信息的链接:


ViewPager的一个问题是您无法控制猛扑手势的距离和速度参数。
almalkawi 2012年

图库中未使用ViewPager。
安东尼

18

有一个内置界面,您可以直接将其用于所有手势:
以下是基本用户的说明: 在此处输入图片说明 有两种进口商品请谨慎选择两者 在此处输入图片说明 在此处输入图片说明


1
下一步是什么?如何将该监听器设置为特定视图?如果此视图是片段的一部分,该怎么办?
Stan

16

在网络(和此页面)上有一些使用ViewConfiguration的主张。getScaledTouchSlop()的设备缩放值SWIPE_MIN_DISTANCE

getScaledTouchSlop()用于“ 滚动阈值”距离,而不是滑动。滚动阈值距离必须小于“页面之间的摆动”阈值距离。例如,此函数在我的Samsung GS2上返回12个像素,此页面中引用的示例大约为100个像素。

使用API​​级别8(Android 2.2,Froyo),您就有了getScaledPagingTouchSlop()用于页面滑动的。在我的设备上,它返回24(像素)。因此,如果您的API级别小于8,我认为“ 2 * getScaledTouchSlop()”应该是“标准”滑动阈值。但是我的应用程序的用户在小屏幕上告诉我,它太少了……在我的应用程序中,您可以垂直滚动,而水平更改页面。使用建议的值,他们有时会更改页面而不是滚动。


13

也作为一个较小的增强。

try / catch块的主要原因是,对于初始运动,e1可能为null。除了try / catch之外,还包括对null和return的测试。类似于以下内容

if (e1 == null || e2 == null) return false;
try {
...
} catch (Exception e) {}
return false;

12

这里有很多很好的信息。不幸的是,尽管有很多人认为这对于许多应用程序是必不可少的,但许多此类处理代码分散在各个站点的各个完成状态。

我花了一些时间来创建一个监听器,以验证是否满足适当的条件。我添加了一个页面文件监听器,该监听器添加了更多检查以确保文件文件达到页面文件文件的阈值。这两个侦听器都使您可以轻松地将摆动限制在水平或垂直轴上。您可以看到它如何在视图中用于滑动图像。我承认这里的人们已经完成了大部分研究工作,我只是将它们汇总到一个可用的库中。

最近几天代表我第一次尝试在Android上进行编码;期望更多


我想通过2个手指来实现滑动手势。请帮帮我!
Gaurav Arora

12

如果有人想要一个可行的实现,那么这是最上面两个答案的组合答案。

package com.yourapplication;

import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

public abstract class OnSwipeListener implements View.OnTouchListener {

    private final GestureDetector gestureDetector;

    public OnSwipeListener(Context context){
        gestureDetector = new GestureDetector(context, new OnSwipeGestureListener(context));
        gestureDetector.setIsLongpressEnabled(false);
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
    }

    private final class OnSwipeGestureListener extends GestureDetector.SimpleOnGestureListener {

        private final int minSwipeDelta;
        private final int minSwipeVelocity;
        private final int maxSwipeVelocity;

        private OnSwipeGestureListener(Context context) {
            ViewConfiguration configuration = ViewConfiguration.get(context);
            // We think a swipe scrolls a full page.
            //minSwipeDelta = configuration.getScaledTouchSlop();
            minSwipeDelta = configuration.getScaledPagingTouchSlop();
            minSwipeVelocity = configuration.getScaledMinimumFlingVelocity();
            maxSwipeVelocity = configuration.getScaledMaximumFlingVelocity();
        }

        @Override
        public boolean onDown(MotionEvent event) {
            // Return true because we want system to report subsequent events to us.
            return true;
        }

        // NOTE: see http://stackoverflow.com/questions/937313/android-basic-gesture-detection
        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX,
                               float velocityY) {

            boolean result = false;
            try {
                float deltaX = event2.getX() - event1.getX();
                float deltaY = event2.getY() - event1.getY();
                float absVelocityX = Math.abs(velocityX);
                float absVelocityY = Math.abs(velocityY);
                float absDeltaX = Math.abs(deltaX);
                float absDeltaY = Math.abs(deltaY);
                if (absDeltaX > absDeltaY) {
                    if (absDeltaX > minSwipeDelta && absVelocityX > minSwipeVelocity
                            && absVelocityX < maxSwipeVelocity) {
                        if (deltaX < 0) {
                            onSwipeLeft();
                        } else {
                            onSwipeRight();
                        }
                    }
                    result = true;
                } else if (absDeltaY > minSwipeDelta && absVelocityY > minSwipeVelocity
                        && absVelocityY < maxSwipeVelocity) {
                    if (deltaY < 0) {
                        onSwipeTop();
                    } else {
                        onSwipeBottom();
                    }
                }
                result = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
    }

    public void onSwipeLeft() {}

    public void onSwipeRight() {}

    public void onSwipeTop() {}

    public void onSwipeBottom() {}
}

感谢您的出色实施。此外,我会建议检查absDeltaY > minSwipeDeltaabsVelocityY > minSwipeVelocityabsVelocityY < maxSwipeVelocity只有在情况下,如果minSwipeDelta != getScaledTouchSlopminSwipeVelocity != getScaledMinimumFlingVelocitymaxSwipeVelocity != getScaledMaximumFlingVelocity,即只检查,如果这些所谓的“默认”(我的意思是getScaledTouchSlop,getScaledMinimumFlingVelocity,getScaledMaximumFlingVelocity)值是根据缩放或更改了自己的愿望。
Elia12345

问题的关键是,根据源代码,所提到的“默认”值已经由GestureDetector检查,如果他们被证实OnFling时才会触发(由触发仅发生时的方式ACTION_UP,而不是ACTION_MOVEACTION_POINTER_UP,即只完全实现的手势的结果)。(我尚未检查其他API版本,因此请多加评论)。
Elia12345

11

您可以使用droidQuery库来处理变化,单击,长按和自定义事件。该实现基于我之前的以下答案,但droidQuery提供了一种简洁的简单语法:

//global variables    private boolean isSwiping = false;
private SwipeDetector.Direction swipeDirection = null;
private View v;//must be instantiated before next call.

//swipe-handling code
$.with(v).swipe(new Function() {
    @Override
    public void invoke($ droidQuery, Object... params) {
        if (params[0] == SwipeDetector.Direction.START)
            isSwiping = true;
        else if (params[0] == SwipeDetector.Direction.STOP) {
            if (isSwiping) {                    isSwiping = false;
                if (swipeDirection != null) {
                    switch(swipeDirection) {
                        case DOWN :                                //TODO: Down swipe complete, so do something
                            break;
                        case UP :
                            //TODO: Up swipe complete, so do something
                            break;
                        case LEFT :
                            //TODO: Left swipe complete, so do something
                            break;
                        case RIGHT :
                            //TODO: Right swipe complete, so do something
                            break;
                        default :                                break;
                    }
                }                }
        }
        else {
            swipeDirection = (SwipeDetector.Direction) params[0];
        }
    }
});

原始答案

该答案结合了此处其他答案的组成部分。它由SwipeDetector类组成,该类具有用于侦听事件的内部接口。我还提供了一个, RelativeLayout以显示如何覆盖ViewonTouch方法以同时允许滑动事件和其他检测到的事件(例如单击或长按)。

滑动检测器

package self.philbrown;

import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

/**
 * Detect Swipes on a per-view basis. Based on original code by Thomas Fankhauser on StackOverflow.com,
 * with adaptations by other authors (see link).
 * @author Phil Brown
 * @see <a href="http://stackoverflow.com/questions/937313/android-basic-gesture-detection">android-basic-gesture-detection</a>
 */
public class SwipeDetector implements View.OnTouchListener
{
    /**
     * The minimum distance a finger must travel in order to register a swipe event.
     */
    private int minSwipeDistance;

    /** Maintains a reference to the first detected down touch event. */
    private float downX, downY;

    /** Maintains a reference to the first detected up touch event. */
    private float upX, upY;

    /** provides access to size and dimension contants */
    private ViewConfiguration config;

    /**
     * provides callbacks to a listener class for various swipe gestures.
     */
    private SwipeListener listener;

    public SwipeDetector(SwipeListener listener)
    {
        this.listener = listener;
    }


    /**
     * {@inheritDoc}
     */
    public boolean onTouch(View v, MotionEvent event)
    {
        if (config == null)
        {
                config = ViewConfiguration.get(v.getContext());
                minSwipeDistance = config.getScaledTouchSlop();
        }

        switch(event.getAction())
        {
        case MotionEvent.ACTION_DOWN:
            downX = event.getX();
            downY = event.getY();
            return true;
        case MotionEvent.ACTION_UP:
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

            // swipe horizontal?
            if(Math.abs(deltaX) > minSwipeDistance)
            {
                // left or right
                if (deltaX < 0)
                {
                        if (listener != null)
                        {
                                listener.onRightSwipe(v);
                                return true;
                        }
                }
                if (deltaX > 0)
                {
                        if (listener != null)
                        {
                                listener.onLeftSwipe(v);
                                return true;
                        }
                }
            }

            // swipe vertical?
            if(Math.abs(deltaY) > minSwipeDistance)
            {
                // top or down
                if (deltaY < 0)
                {
                        if (listener != null)
                        {
                                listener.onDownSwipe(v);
                                return true;
                        }
                }
                if (deltaY > 0)
                {
                        if (listener != null)
                        {
                                listener.onUpSwipe(v);
                                return true;
                        }
                }
            }
        }
        return false;
    }

    /**
     * Provides callbacks to a registered listener for swipe events in {@link SwipeDetector}
     * @author Phil Brown
     */
    public interface SwipeListener
    {
        /** Callback for registering a new swipe motion from the bottom of the view toward its top. */
        public void onUpSwipe(View v);
        /** Callback for registering a new swipe motion from the left of the view toward its right. */
        public void onRightSwipe(View v);
        /** Callback for registering a new swipe motion from the right of the view toward its left. */
        public void onLeftSwipe(View v);
        /** Callback for registering a new swipe motion from the top of the view toward its bottom. */
        public void onDownSwipe(View v);
    }
}

滑动拦截器视图

package self.philbrown;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.RelativeLayout;

import com.npeinc.module_NPECore.model.SwipeDetector;
import com.npeinc.module_NPECore.model.SwipeDetector.SwipeListener;

/**
 * View subclass used for handling all touches (swipes and others)
 * @author Phil Brown
 */
public class SwipeInterceptorView extends RelativeLayout
{
    private SwipeDetector swiper = null;

    public void setSwipeListener(SwipeListener listener)
    {
        if (swiper == null)
            swiper = new SwipeDetector(listener);
    }

    public SwipeInterceptorView(Context context) {
        super(context);
    }

    public SwipeInterceptorView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SwipeInterceptorView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onTouchEvent(MotionEvent e)
    {
        boolean swipe = false, touch = false;
        if (swiper != null)
            swipe = swiper.onTouch(this, e);
        touch = super.onTouchEvent(e);
        return swipe || touch;
    }
}

1
我尝试在包含可点击元素的视图上实现此功能。当在可点击元素上滑动时(例如,已注册onItemClick侦听器的列表视图),则永远不会调用onTouchEvent。因此,用户无法在可点击元素上开始滑动,这对我来说是不幸的,我仍在尝试解决该问题,因为我们的可点击元素占用了相当多的视图空间,并且我们仍然需要滑动支持对于整个视图。如果滑动未从可点击元素开始,则说明效果很好。
罗丹(Lo-Tan)

@罗谭,出现这种情况是因为您可点击产品子视图,因此是在上面SwipeInterceptorView,所以它的点击先处理。您可以通过实现来实现自己的点击机制,以解决此问题onTouchListener,或者作为一种变通办法,您可以监听长点击而不是点击(请参阅参考资料View.setOnLongClickListener)。
2013年

我实际上正在尝试此刻。如果他们开始拖动,也可以取消点击事件:)非常感谢。
罗丹(Lo-Tan)

一种解决方案是将滑动检测器连接到应用程序中的每个视图。另一个方法是在SwipeInterceptorView中实现onInterceptTouchEvent。
Edward Falk 2015年

7

我知道现在回答还为时已晚,但我仍在发布ListView的轻扫检测,即如何在ListView Item中使用Swipe Touch Listener

Reference:Exterminator13(此页面中的答案之一)

制作一个ActivitySwipeDetector.class

package com.example.wocketapp;

import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

public class ActivitySwipeDetector implements View.OnTouchListener 
{
    static final String logTag = "SwipeDetector";
    private SwipeInterface activity;
    private float downX, downY;
    private long timeDown;
    private final float MIN_DISTANCE;
    private final int VELOCITY;
    private final float MAX_OFF_PATH;

    public ActivitySwipeDetector(Context context, SwipeInterface activity)
    {
        this.activity = activity;
        final ViewConfiguration vc = ViewConfiguration.get(context);
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        MIN_DISTANCE = vc.getScaledPagingTouchSlop() * dm.density;
        VELOCITY = vc.getScaledMinimumFlingVelocity();
        MAX_OFF_PATH = MIN_DISTANCE * 2;
    }

    public void onRightToLeftSwipe(View v) 
    {
        Log.i(logTag, "RightToLeftSwipe!");
        activity.onRightToLeft(v);
    }

    public void onLeftToRightSwipe(View v) 
    {
        Log.i(logTag, "LeftToRightSwipe!");
        activity.onLeftToRight(v);
    }

    public boolean onTouch(View v, MotionEvent event) 
    {
        switch (event.getAction()) 
        {
            case MotionEvent.ACTION_DOWN:
            {
                Log.d("onTouch", "ACTION_DOWN");
                timeDown = System.currentTimeMillis();
                downX = event.getX();
                downY = event.getY();
                v.getParent().requestDisallowInterceptTouchEvent(false);
                return true;
            }

        case MotionEvent.ACTION_MOVE:
            {
                float y_up = event.getY();
                float deltaY = y_up - downY;
                float absDeltaYMove = Math.abs(deltaY);

                if (absDeltaYMove > 60) 
                {
                    v.getParent().requestDisallowInterceptTouchEvent(false);
                } 
                else
                {
                    v.getParent().requestDisallowInterceptTouchEvent(true);
                }
            }

            break;

            case MotionEvent.ACTION_UP: 
            {
                Log.d("onTouch", "ACTION_UP");
                long timeUp = System.currentTimeMillis();
                float upX = event.getX();
                float upY = event.getY();

                float deltaX = downX - upX;
                float absDeltaX = Math.abs(deltaX);
                float deltaY = downY - upY;
                float absDeltaY = Math.abs(deltaY);

                long time = timeUp - timeDown;

                if (absDeltaY > MAX_OFF_PATH) 
                {
                    Log.e(logTag, String.format(
                            "absDeltaY=%.2f, MAX_OFF_PATH=%.2f", absDeltaY,
                            MAX_OFF_PATH));
                    return v.performClick();
                }

                final long M_SEC = 1000;
                if (absDeltaX > MIN_DISTANCE && absDeltaX > time * VELOCITY / M_SEC) 
                {
                     v.getParent().requestDisallowInterceptTouchEvent(true);
                    if (deltaX < 0) 
                    {
                        this.onLeftToRightSwipe(v);
                        return true;
                    }
                    if (deltaX > 0) 
                    {
                        this.onRightToLeftSwipe(v);
                        return true;
                    }
                }
                else 
                {
                    Log.i(logTag,
                            String.format(
                                    "absDeltaX=%.2f, MIN_DISTANCE=%.2f, absDeltaX > MIN_DISTANCE=%b",
                                    absDeltaX, MIN_DISTANCE,
                                    (absDeltaX > MIN_DISTANCE)));
                    Log.i(logTag,
                            String.format(
                                    "absDeltaX=%.2f, time=%d, VELOCITY=%d, time*VELOCITY/M_SEC=%d, absDeltaX > time * VELOCITY / M_SEC=%b",
                                    absDeltaX, time, VELOCITY, time * VELOCITY
                                            / M_SEC, (absDeltaX > time * VELOCITY
                                            / M_SEC)));
                }

                 v.getParent().requestDisallowInterceptTouchEvent(false);

            }
        }
        return false;
    }
    public interface SwipeInterface 
    {

        public void onLeftToRight(View v);

        public void onRightToLeft(View v);
    }

}

从您的活动类中调用它,如下所示:

yourLayout.setOnTouchListener(new ActivitySwipeDetector(this, your_activity.this));

并且不要忘记实现 SwipeInterface,它将为您提供两个@override方法:

    @Override
    public void onLeftToRight(View v) 
    {
        Log.e("TAG", "L to R");
    }

    @Override
    public void onRightToLeft(View v) 
    {
        Log.e("TAG", "R to L");
    }

我发现,MAX_OFF_PATH = 5 * vc.getScaledPagingTouchSlop()对于自然滑动微小弧度的拇指滑动来说,a 更舒适。
2014年

4

手势是那些微妙的动作,可触发触摸屏和用户之间的交互。它持续的时间介于屏幕第一次触摸到最后一根手指离开表面的时间。

Android为我们提供了一个名为GestureDetector的类,通过该类我们可以检测常见的手势,例如上下轻击,垂直和水平轻扫(滑动),长按和短按,双击等。并为他们附加听众。

使我们的Activity 类实现GestureDetector.OnDoubleTapListener(用于双击手势检测)和GestureDetector.OnGestureListener接口,并实现所有抽象方法。有关更多信息。您可以访问https://developer.android.com/training/gestures/detector.html礼貌

用于演示测试。手势检测器演示


4

如果您不想创建单独的类或使代码复杂,
则只需在OnTouchListener内创建一个GestureDetector变量,并使代码更容易

namVyuVar可以是您需要在其上设置列表器的View的任何名称

namVyuVar.setOnTouchListener(new View.OnTouchListener()
{
    @Override
    public boolean onTouch(View view, MotionEvent MsnEvtPsgVal)
    {
        flingActionVar.onTouchEvent(MsnEvtPsgVal);
        return true;
    }

    GestureDetector flingActionVar = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener()
    {
        private static final int flingActionMinDstVac = 120;
        private static final int flingActionMinSpdVac = 200;

        @Override
        public boolean onFling(MotionEvent fstMsnEvtPsgVal, MotionEvent lstMsnEvtPsgVal, float flingActionXcoSpdPsgVal, float flingActionYcoSpdPsgVal)
        {
            if(fstMsnEvtPsgVal.getX() - lstMsnEvtPsgVal.getX() > flingActionMinDstVac && Math.abs(flingActionXcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Right to Left fling

                return false;
            }
            else if (lstMsnEvtPsgVal.getX() - fstMsnEvtPsgVal.getX() > flingActionMinDstVac && Math.abs(flingActionXcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Left to Right fling

                return false;
            }

            if(fstMsnEvtPsgVal.getY() - lstMsnEvtPsgVal.getY() > flingActionMinDstVac && Math.abs(flingActionYcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Bottom to Top fling

                return false;
            }
            else if (lstMsnEvtPsgVal.getY() - fstMsnEvtPsgVal.getY() > flingActionMinDstVac && Math.abs(flingActionYcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Top to Bottom fling

                return false;
            }
            return false;
        }
    });
});

3

所有人:不要忘了MotionEvent.ACTION_CANCEL的情况:

它会在没有ACTION_UP的情况下进行30%的滑动

在这种情况下等于ACTION_UP


2

我创建了一个更通用的Class,加入了Tomas的类,并添加了一个将事件发送到您的Activity或Fragment的接口。它将在构造函数上注册侦听器,因此请确保实现接口,否则将抛出ClassCastException。接口返回该类中定义的四个最终int之一,并将返回对其进行激活的视图。

import android.app.Activity;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class SwipeDetector implements View.OnTouchListener{

    static final int MIN_DISTANCE = 100;
    private float downX, downY, upX, upY;
    public final static int RIGHT_TO_LEFT=1;
    public final static int LEFT_TO_RIGHT=2;
    public final static int TOP_TO_BOTTOM=3;
    public final static int BOTTOM_TO_TOP=4;
    private View v;

    private onSwipeEvent swipeEventListener;


    public SwipeDetector(Activity activity,View v){
        try{
            swipeEventListener=(onSwipeEvent)activity;
        }
        catch(ClassCastException e)
        {
            Log.e("ClassCastException",activity.toString()+" must implement SwipeDetector.onSwipeEvent");
        } 
        this.v=v;
    }
    public SwipeDetector(Fragment fragment,View v){
        try{
            swipeEventListener=(onSwipeEvent)fragment;
        }
        catch(ClassCastException e)
        {
            Log.e("ClassCastException",fragment.toString()+" must implement SwipeDetector.onSwipeEvent");
        } 
        this.v=v;
    }


    public void onRightToLeftSwipe(){   
        swipeEventListener.SwipeEventDetected(v,RIGHT_TO_LEFT);
    }

    public void onLeftToRightSwipe(){   
        swipeEventListener.SwipeEventDetected(v,LEFT_TO_RIGHT);
    }

    public void onTopToBottomSwipe(){   
        swipeEventListener.SwipeEventDetected(v,TOP_TO_BOTTOM);
    }

    public void onBottomToTopSwipe(){
        swipeEventListener.SwipeEventDetected(v,BOTTOM_TO_TOP);
    }

    public boolean onTouch(View v, MotionEvent event) {
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

            //HORIZONTAL SCROLL
            if(Math.abs(deltaX) > Math.abs(deltaY))
            {
                if(Math.abs(deltaX) > MIN_DISTANCE){
                    // left or right
                    if(deltaX < 0) 
                    {
                        this.onLeftToRightSwipe();
                        return true;
                    }
                    if(deltaX > 0) {
                        this.onRightToLeftSwipe();
                        return true; 
                    }
                }
                else {
                    //not long enough swipe...
                    return false; 
                }
            }
            //VERTICAL SCROLL
            else 
            {
                if(Math.abs(deltaY) > MIN_DISTANCE){
                    // top or down
                    if(deltaY < 0) 
                    { this.onTopToBottomSwipe();
                    return true; 
                    }
                    if(deltaY > 0)
                    { this.onBottomToTopSwipe(); 
                    return true;
                    }
                }
                else {
                    //not long enough swipe...
                    return false;
                }
            }

            return true;
        }
        }
        return false;
    }
    public interface onSwipeEvent
    {
        public void SwipeEventDetected(View v , int SwipeType);
    }

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.