题 网格布局上的手势检测


我想得到 fling 手势检测在我的Android应用程序中工作。

我拥有的是一个 GridLayout 包含9 ImageView秒。来源可以在这里找到: Romain Guys的网格布局

我拿的那个文件是来自Romain Guy的 Photostream应用程序 并且只是略微适应。

对于简单的点击情况,我只需要设置 onClickListener 为每个人 ImageView 我补充说是主要的 activity 实现 View.OnClickListener。实现识别a的东西似乎无比复杂 fling。我认为这是因为它可能跨越 views

  • 如果我的活动实现了 OnGestureListener 我不知道该怎么做 将其设置为手势监听器 该 Grid 或者 Image 看到我 加。

    public class SelectFilterActivity extends Activity implements
       View.OnClickListener, OnGestureListener { ...
    
  • 如果我的活动实现了 OnTouchListener 然后我没有 onFling 方法 override (它有 两个事件作为参数允许我 确定投掷是否是 值得注意的)。

    public class SelectFilterActivity extends Activity implements
        View.OnClickListener, OnTouchListener { ...
    
  • 如果我做一个习惯 View, 喜欢 GestureImageView 延伸 ImageView 我不知道如何告诉活动a 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传递我的子图像视图 GestureDetector 作为新子类的构造函数参数 ImageView 我创造了什么?

这是我想要获得的非常简单的活动 fling 检测工作: SelectFilterActivity(改编自photostream)

我一直在看这些来源:

到目前为止,对我来说没有任何作用,我希望得到一些指示。


1000
2018-06-01 23:35


起源


嘿,我的GestureDetector遇到了问题,我希望你能看看它。我发布了一个问题,我的代码是: stackoverflow.com/questions/11742051/... - Andy


答案:


谢谢 代码幕府将军,我的代码我适应了我的情况。

让您的活动实施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);
}

帖子'fling'舞蹈是可选的,但鼓励。


776
2018-04-20 17:26



谢谢你的代码!这非常有帮助。然而,在试图让手势工作时,我遇到了一个非常令人沮丧的问题。在我的SimpleOnGestureListener中,我必须覆盖onDown才能注册我的任何手势。它可以返回true但我必须定义。 P.S:我不知道它是我的api版本还是我的硬件,但我在HTC Droid Eris上使用1.5。 - Cdsboy
我也必须实现onDown。非常好的Cdsboy指出这一点! - corgrath
谢谢,OnClickListener可以解决问题 - Azlam
OnDown()应该返回false,否则将不会调用Activity的onClick()。 - Jarrod Smith
developer.android.com/training/gestures/detector.html 似乎说你需要为onDown返回true而不管onClick(),否则手势将无法识别? - emschorsch


上面的答案之一提到处理不同的像素密度,但建议手动计算滑动参数。值得注意的是,您实际上可以从系统中获得缩放的,合理的值 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)

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


203
2018-04-21 10:24



我用 swipeMinDistance = vc.getScaledPagingTouchSlop() 和 swipeMaxOffPath = vc.getScaledTouchSlop()。 - Thomas Ahle
getScaledTouchSlop 尴尬地给了我很少的补偿结果。例如,540高屏幕上只有24个像素,用手指很难将其保持在范围内。 :S - WonderCsabo


我做的有点不同,并写了一个额外的探测器类来实现 View.onTouchListener 

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

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

其中id.lowestLayout是布局层次结构中最低视图的id.xxx,而LowestLayout被声明为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;
}

}

对我来说真的很好用!


140
2018-01-10 16:11



这实际上使我更容易应用手势功能,并需要“少”布线:D谢谢@Thomas - nemesisfixx
这看起来像一个简洁的实用程序类 - 但我认为你的四个... swipe()方法应该是接口 - Someone Somewhere
这些回报不应该存在(行“我们不消耗事件”),不是吗?它禁用垂直滚动功能。 - Marek Sebera
上面的代码片段中有一些微妙的错误。 - Jeffrey Blattman
具体来说,是onTouch()方法。首先,如果delta X不够大,它将返回而不检查delta Y.结果是从不检测左右滑动。第二,如果没有通过滑动,它也不应该返回true。第三,它不应该在行动失败时返回真实。这可以防止像onClick这样的任何其他监听器工作。 - Jeffrey Blattman


我稍微修改并修复了解决方案 Thomas Fankhauser

整个系统由两个文件组成, SwipeInterface 和 ActivitySwipeDetector


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 滑动事件 被称为。

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

90
2018-02-18 09:45



我喜欢这个版本。其他设计决定,但看起来很棒! - Thomas Fankhauser
@ThomasFankhauser感谢托马斯 - Marek Sebera
我稍微修改了一下,看到了 v.performClick();,如果在同一视图上设置,则用于不向OnClickListener使用事件 - Marek Sebera
嗨,我是一个初学者,所以这个问题可能非常明显或微不足道,但请回答。您编写的部分,用作:ActivitySwipeDetector swipe = new ActivitySwipeDetector(this);这个声明将是MainActivity的一部分,对吗?然后,“this”将是MainActivity的活动。而构造函数采用SwipeInterface的实例。请帮助我。非常感谢。 - Chocolava
@Chocolava创建新问题,评论不是这样问的好地方。 - Marek Sebera


上面的滑动手势检测器代码非常有用!但是,您可以通过使用以下相对值来使此解决方案密度不可知 (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);

61
2017-07-18 01:00



提出这个+1。请注意,在API 4中引入了DensityMetrics.densityDpi。为了与API 1向后兼容,请改用DensityMetrics.density。然后,这会将计算更改为SWIPE_MIN_DISTANCE * dm.density。 - Thane Anthem
你在哪里得到160.0f的数字? - Igor Ganapolsky
developer.android.com/guide/practices/screens_support.html 密度无关像素(dp)dp单位到屏幕像素的转换很简单:px = dp *(dpi / 160) - paiego
我一直在寻找这个。互联网上没有onFling()的例子,这会导致用户体验不佳。谢谢! - Sandy
160.0f来自160 DPI,它是DP(密度独立像素)所基于的标准密度。 public static final int DENSITY_MEDIUM在API级别4中添加用于中密度屏幕的标准量化DPI。常数值:160(0x000000a0) - paiego


我提出的解决方案版本 Thomas Fankhauser 和 Marek 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;
    }

}

32
2018-04-23 19:54



任何人都可以告诉我如何打电话给班级。 ActivitySwipeDetector swipe = new ActivitySwipeDetector(this);显然是给出错误,因为没有这样的构造函数。我应该给ActivitySwipeDetector swipe = new ActivitySwipeDetector(this,null); - abdfahim
@AbdullahFahim ActivitySwipeDetector(this,YourActivity.this); - Anton Kashpor


这个问题有点陈旧,并于2011年7月谷歌发布了 兼容性包,修订版3) 其中包括 ViewPager 适用于Android 1.6以上版本。该 GestureListener 发布此问题的答案在Android上感觉不是很优雅。如果您正在寻找在Android图库中切换照片或在新的Play Market应用中切换视图时使用的代码,那么肯定是 ViewPager

以下是一些链接以获取更多信息:


22
2018-04-05 14:28



ViewPager的一个问题是您无法控制拖动手势的距离和速度参数。 - almalkawi
ViewPager未在库中使用。 - Anthony


有一个内置界面,您可以直接使用所有手势:
以下是基本级别用户的说明: enter image description here 有2个进口要谨慎选择两者是不同的 enter image description here enter image description here


14
2018-04-12 21:14



接下来的步骤是什么?如何将该侦听器设置为特定视图?如果这个视图是片段的一部分怎么办? - Stan


也作为一个小的增强。

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

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

11
2017-11-02 23:14





这里有很多很棒的信息。不幸的是,很多这种处理代码分散在各种完成状态的各个站点上,尽管人们会认为这对许多应用程序来说都是必不可少的。

我花时间创造了一个 聆听者 验证是否符合适当的条件。我添加了一个 翻页听众 添加更多检查以确保flings满足页面flings的阈值。这两种侦听器都允许您轻松地将晃动限制在水平轴或垂直轴上。你可以看到它是如何用于一个 查看滑动图像。我承认这里的人已完成了大部分的研究 - 我只是把它放在一个可用的库中。

最近几天代表我第一次尝试在Android上编码;期望 多得多 来。


10
2017-12-07 07:49



我想用两根手指实现滑动手势。请帮帮我! - gauravsapiens