【问题标题】:Fling gesture detection on grid layout网格布局上的 Fling 手势检测
【发布时间】:2013-02-25 07:13:46
【问题描述】:

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

我拥有的是一个 GridLayout,其中包含 9 个 ImageViews。来源可以在这里找到:Romain Guys's Grid Layout。

我拍摄的那个文件来自 Romain Guy 的 Photostream application,只是稍作修改。

对于简单的点击情况,我只需为我添加的每个ImageView 设置onClickListener,作为实现View.OnClickListener 的主要activity。实现识别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,例如扩展 ImageView 的 GestureImageView,我不知道如何告诉活动从视图中发生了 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;
        }
    }
});

是否可以在我的屏幕顶部放置一个透明视图来捕捉弹幕?

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

这是我试图让fling 检测工作的非常简单的活动:SelectFilterActivity (Adapted from photostream)。

我一直在查看这些来源:

到目前为止,没有什么对我有用,我希望得到一些指导。

【问题讨论】:

标签: android listener gesture-recognition


【解决方案1】:

感谢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);
}

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

【讨论】:

  • 感谢您提供此代码!这很有帮助。但是,在尝试让手势起作用时,我遇到了一个非常非常令人沮丧的问题。在我的 SimpleOnGestureListener 中,我必须重写 onDown 才能注册任何手势。它只能返回 true,但必须定义 i。 P.S:我不知道是我的 api 版本还是我的硬件,但我在 HTC Droid Eris 上使用的是 1.5。
  • 我试过你的代码,如果我滑动或点击都没关系(用我的鼠标,因为我在模拟器中工作),我总是得到我在 onClick 方法中定义的 Toast,所以模拟器检测到只有点击,没有滑动。为什么会这样?
  • 我试过这段代码,但它不起作用。当我将 onClick 侦听器应用于我的画廊视图中的一个子视图时,仍然无法滚动
  • Iomza:您是否尝试过放置 break 语句并单步执行您的代码?
  • 感谢使用内部类!非常干净的方法。
【解决方案2】:

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

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

【讨论】:

  • 我使用swipeMinDistance = vc.getScaledPagingTouchSlop()和swipeMaxOffPath = vc.getScaledTouchSlop()。
  • getScaledTouchSlop 尴尬地给了我很少的偏移结果。例如,在 540 高的屏幕上只有 24 像素,这很难用手指将其保持在范围内。 :S
【解决方案3】:

我做的有点不同,写了一个额外的检测器类来实现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;
}

}

对我来说真的很好用!

【讨论】:

  • 这实际上让我更容易应用手势功能,并且需要“更少”接线:D 谢谢@Thomas
  • 这看起来像一个简洁的实用程序类 - 但我认为你的四个 on...swipe() 方法应该是接口
  • 这些回报不应该存在(行“我们不消费事件”),不是吗?它禁用垂直滚动功能。
  • 特别是 onTouch() 方法。首先,如果 delta X 不够大,它会在不检查 delta Y 的情况下返回。结果是永远不会检测到左右滑动。其次,如果发现没有滑动,它也不应该返回 true。第三,它不应该在 action down 时返回 true。这会阻止像 onClick 这样的任何其他监听器工作。
  • @Piotr 只要持有引用的对象与活动本身的范围相同,这不是问题。当您在范围比活动更大的地方保留对活动的引用时会出现问题......例如来自静态成员。
【解决方案4】:

我对 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 的方法,您可以找出调用了Swipe 事件 的View。

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

【讨论】:

  • 我又稍微修改了一下,见v.performClick();,如果设置在同一个视图上,则用于不消费事件到OnClickListener
  • 嗨,我是一个初学者,所以这个问题可能非常明显或微不足道,但请回答。你写的部分,它被用作:ActivitySwipeDetector swipe = new ActivitySwipeDetector(this);该语句将成为 MainActivity 的一部分,对吗?那么,“this”将是 MainActivity 的一个活动。而构造函数采用 SwipeInterface 的实例。请在这里帮助我。非常感谢。
  • @Chocolava 创建新问题,评论不是这样问的好地方。
  • @MarekSebera 这不适用于 ScrollView 和 ListView?如何处理?
  • @silentbang 再次,这里不是问这些问题的地方。请创建新的问题线程。
【解决方案5】:

上面的滑动手势检测器代码非常有用!但是,您可能希望通过使用以下相对值 (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);

【讨论】:

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

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;
    }

}

【讨论】:

  • 谁能告诉我如何打电话给班级。 ActivitySwipeDetector swipe = new ActivitySwipeDetector(this);显然是给出错误,因为没有这样的构造函数。我应该给 ActivitySwipeDetector swipe = new ActivitySwipeDetector(this,null);
  • @AbdullahFahim ActivitySwipeDetector(this, YourActivity.this);
【解决方案7】:

这个问题有点老了,2011 年 7 月,Google 发布了 Compatibility Package, revision 3),其中包括适用于 Android 1.6 以上的 ViewPager。为这个问题发布的GestureListener 答案在 Android 上感觉不是很优雅。如果您正在寻找在 Android 图库中的照片之间切换或在新的 Play Market 应用中切换视图所使用的代码,那么绝对是ViewPager。

以下是更多信息的链接:

【讨论】:

  • ViewPager 的一个问题是您无法控制投掷手势的距离和速度参数。
  • 图库中未使用 ViewPager。
【解决方案8】:

有一个可直接用于所有手势的内置界面:
这里是一个基础级用户的解释: 有2个进口要小心选择两者不同

【讨论】:

  • 接下来的步骤是什么?如何将该侦听器设置为特定视图?如果这个视图是片段的一部分呢?
【解决方案9】:

网络(和此页面)有一些建议使用 ViewConfiguration。getScaledTouchSlop() 为SWIPE_MIN_DISTANCE 设置设备缩放值。

getScaledTouchSlop() 用于“滚动阈值”距离,而不是滑动。滚动阈值距离必须小于“页面之间的摆动”阈值距离。例如,这个函数在我的三星 GS2 上返回 12 像素,而本页引用的示例在 100 像素左右。

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

【讨论】:

    【解决方案10】:

    也作为一个小改进。

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

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

    【讨论】:

      【解决方案11】:

      如果有人想要一个可行的实现,这是顶部两个答案的组合答案。

      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 &gt; minSwipeDelta,absVelocityY &gt; minSwipeVelocity,absVelocityY &lt; maxSwipeVelocity,仅在minSwipeDelta != getScaledTouchSlop,minSwipeVelocity != @ 987654329@, maxSwipeVelocity != getScaledMaximumFlingVelocity,即只检查这些所谓的“默认”(我的意思是getScaledTouchSlop、getScaledMinimumFlingVelocity、getScaledMaximumFlingVelocity)值是否根据您自己的意愿进行缩放或更改.
      • 重点是,根据source code,提到的“默认”值已经被GestureDetector检查过,只有在确认后才会触发OnFling(顺便说一下,触发只发生在ACTION_UP 的情况,而不是 ACTION_MOVE 或 ACTION_POINTER_UP,即仅作为完全实现手势的结果)。 (我没有检查过其他 API 版本,因此感谢 cmets)。
      【解决方案12】:

      这里有很多很棒的信息。不幸的是,尽管人们会认为这对许多应用程序来说是必不可少的,但很多这样的丢弃处理代码散布在不同的站点上,处于不同的完成状态。

      我花时间创建了一个fling listener,用于验证是否满足适当的条件。我添加了一个page fling listener,它添加了更多检查以确保投掷符合页面投掷的阈值。这两个侦听器都允许您轻松地将投掷限制在水平或垂直轴上。您可以在view for sliding images 中查看它是如何使用的。我承认这里的人已经完成了大部分研究——我只是把它放在一个可用的库中。

      这几天是我第一次尝试在 Android 上进行编码;期待much more 的到来。

      【讨论】:

      • 我想通过两根手指实现滑动手势。请帮帮我!
      【解决方案13】:

      您可以使用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 来展示如何覆盖View 的onTouch 方法以允许滑动事件和其他检测到的事件(例如单击或长按)。

      SwipeDetector

      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;
          }
      }
      

      【讨论】:

      • 我尝试在包含可点击元素的视图上实现这一点。当滑动开始于可点击元素(例如,注册了 onItemClick 侦听器的列表视图)时,永远不会调用 onTouchEvent。因此,用户无法在可点击元素上开始滑动,这对我来说很不幸,我仍在试图弄清楚如何解决这个问题,因为我们的可点击元素占用了相当多的视图空间,我们仍然需要滑动支持对于整个视图。如果滑动不是从可点击元素开始,那么它可以完美运行。
      • @Lo-Tan,这是因为您的可点击项目是子视图,因此位于SwipeInterceptorView 的顶部,因此首先处理其点击。您可以通过实现onTouchListener 来实现您自己的点击机制来解决此问题,或者作为一种变通方法,您可以监听长点击而不是点击(参见View.setOnLongClickListener)。
      • 我实际上正在尝试这样做。或者如果他们开始拖动,可能会取消点击事件:) 非常感谢。
      • 一种解决方案是将滑动检测器附加到应用程序中的每个视图。另一个是在 SwipeInterceptorView 中实现onInterceptTouchEvent。
      【解决方案14】:

      我知道回答为时已晚,但我仍在发布 Swipe Detection for ListView 如何在 ListView 项中使用 Swipe Touch Listener。

      参考: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() 更适合拇指自然滑动以轻微弧度移动。
      【解决方案15】:

      如果您不喜欢创建单独的类或使代码复杂,
      你可以在 OnTouchListener 中创建一个 GestureDetector 变量,让你的代码更简单

      namVyuVar 可以是任何需要设置listner的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;
              }
          });
      });
      

      【讨论】:

        【解决方案16】:

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

        Android 为我们提供了一个名为 GestureDetector 的类,我们可以使用它来检测常见的手势,例如上下轻击、垂直和水平滑动(滑动)、长按和短按、双击等 .并将听众附加到他们身上。

        创建我们的活动class implement GestureDetector.OnDoubleTapListener(用于双击手势检测)和GestureDetector.OnGestureListener interfaces,并实现所有抽象方法。了解更多信息。您可以访问https://developer.android.com/training/gestures/detector.html。 Courtesy

        用于演示测试。GestureDetectorDemo

        【讨论】:

          【解决方案17】:

          致所有人:不要忘记案例 MotionEvent.ACTION_CANCEL:

          它在没有 ACTION_UP 的情况下调用 30% 的滑动

          在这种情况下它等于 ACTION_UP

          【讨论】:

            【解决方案18】:

            我需要一个更通用的 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);
                }
            
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-09-23
              相关资源
              最近更新 更多