手势监听器的onFling方法不一致

11

更新:请查看有关扩展问题的悬赏。

我在ListView上设置了一个GestureDetector。ListView是整个片段,从窗口侧面覆盖另一个部分片段。 我想要给用户关闭它的能力(即,在右侧使用Wunderlist的这个功能是很好的例子)。

这是我的设置:

    gestureListener = new View.OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {

            if (gestureDetector.onTouchEvent(event)) {
                return true;
            }

            return false;
        }
    };
    listView.setOnTouchListener(gestureListener); 

监听器本身:

public class GestureListener extends SimpleOnGestureListener {

        private static final int SWIPE_MIN_DISTANCE = 180;
        private static final int SWIPE_THRESHOLD_VELOCITY = 50;

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                float velocityY) {

            try {
                if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
                        && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {

                    FragmentManager fma = getActivity()
                            .getSupportFragmentManager();



                    FragmentManager fm = getFragmentManager();
                    FragmentTransaction t = fm.beginTransaction();

                    SherlockListFragment mFrag = new RateReviewFragment();

                    t.add(R.id.main_frag, mFrag);
                    t.setCustomAnimations(R.anim.animation_enter,
                            R.anim.animation_leave);
                    t.remove(mFrag);
                    t.commit();

                }
                return false;
            } catch (Exception e) {

            }
            return false;
        }





        @Override
        public void onLongPress(MotionEvent e) {

            // edited out; this opens up a context menu
        }

    }

有时,当 ListView 滚动到底部(或在单击列表行时中断列表滚动)时,GestureListener 就会停止监听。无法进行滑动操作。必须向上滚动才能再次正常工作。
如果有人可以帮我解决这些问题,我将不胜感激!
更新:只剩下一个问题,“监听器停止监听”。
第二次更新:我可能已经找到了原因;只是不知道如何解决:
在记录我的操作后,我发现了一些东西;我在列表中移动的位置越低(并且与之交互的越多),测量结果就越不一致。例如,如果我在列表的顶部左右移动手指一厘米,它会说我移动了200个像素。但是当我遇到上述问题时,它要低得多,也许只有50个像素,即使是相同的1厘米距离。因此,窗口没有关闭,因为它不符合我的 if 条件。
附注:我已经多次说明,问题出现在“与列表交互”时。这意味着:如果我快速从顶部向底部滚动;没有问题;但是如果我缓慢地往下走,也许在屏幕上点击,滚动开关,单击 listView 上的按钮,其中一些会打开一个新的活动(然后回到相同的位置),这就是“交互”。

如果你正在寻找类似于新版Gmail应用程序的从左侧滑出的抽屉式导航栏,你可能需要查看这个链接 - http://developer.android.com/training/implementing-navigation/nav-drawer.html - Varun
@Varun,这不是用于导航的那种滑块。我已经在左侧有了那个。这是一个右侧弹出窗口来显示内容。 - TheLettuceMaster
5个回答

3

试一下这个:

    gestureDetector = new GestureDetector(getActivity(),
            new GestureListener());
    View.OnTouchListener gestureListener = new View.OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
           listView.onTouchEvent(event);
           gestureDetector.onTouchEvent(event);

            return true;
        }
    };
    listView.setOnTouchListener(gestureListener);

并进行以下更改

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
                float distanceX, float distanceY) {
  return true;
}

编辑: 我看到了wunderlist这个应用,如果你想模拟那种功能,请尝试以下方法:

使用onInterceptTouch来处理根布局(其中listView是其子项),在此方法内部,您需要检查用户是否进行了滑动操作,像下面这样:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  if (ev.getAction() == MotionEvent.ACTION_MOVE) {
    if(/* Check your condition here */) { 
      // User has swiped 
      return true;
    }
  }

  return false; // return false so that the event will be passed to child views.
}

for more info Android onInterceptTouchEvent


嘿,感谢您的建议;不幸的是,我仍然偶尔会失去滑动窗口关闭的能力。 - TheLettuceMaster
根据我在Android开发中的经验,你不能总是期望设备提供用户交互所需的正确(更好的词汇是“预期”)触点。这就需要我们(开发者)来处理这种情况,为什么不尝试在onFling()内部的if条件语句中进行调整,以选择最佳条件,使其在8/10的情况下都能正常工作呢?希望这可以帮到你。 - AjOnFire
我同意。如果您看到我的更新问题,我发现了一个巨大的不一致性。虽然我知道有一种方法可以做到这一点,因为Wunderlist完美地执行了它。 - TheLettuceMaster
我在阅读这方面的内容时学到了更多;在您上面的答案中,您有/* 在此检查您的条件 */ - 我的条件需要特定的onFling()属性,例如两个motionEvent值(而不是一个)和velocity。我该如何在这里检查我的条件? - TheLettuceMaster
有很多方法可以做到这一点,@neil在他的回答中指出了其中一个方法,但我建议使用速度追踪器(http://developer.android.com/reference/android/view/VelocityTracker.html)来完成此操作。 - AjOnFire
谢谢你的帮助!你给了我所需的信息,现在我只需要实现它!标记为正确。 - TheLettuceMaster

2

将onTouch更改为以下方式:

@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
     boolean consumed = gestureDetector.onTouchEvent(event);
     if (event.getAction() == MotionEvent.ACTION_MOVE) {
                return false;
     }

    return consumed;
}

谢谢。我会测试一下...问题:我认为onTouch不是SimpleOnGesture方法的一部分吗? - TheLettuceMaster
仍然,gestureDetector 会将触摸事件转发给它。 - Blackbelt
这似乎产生了相反的效果。它破坏了整个“滑动关闭”的功能,而以上三个问题仍然存在。 - TheLettuceMaster
1
你可以滚动ListView,但是fling没有触发吗? - Blackbelt
抱歉,fling已经回来了,但是滚动仍然存在相同的问题...(fling也是如此)。我非常感谢您的帮助,并期待着奖励这些积分。这是我在这个应用程序中遇到的一个长期问题。 - TheLettuceMaster
我已经解决了部分问题,现在所有本地的ListView手势都可以正常工作;剩下的问题是:在某些情况下(特别是当用户缓慢滚动行时,可能会“开始和停止”滚动,也许会上下滚动),onFling不会响应。(如上所述) - TheLettuceMaster

1

我不确定这是否有助于解决所有这些片段问题的背后问题,但可以尝试以下方法:

在调用onTouch之后,在返回false之前检查gestureDetector是否仍然存在(不为null),(通过向listview类发送回调或其他方式)如果不存在,则创建新实例并再次调用。

listView.setOnTouchListener(gestureListener); 

好的,我使用 Log.d 记录了 gestureListener,我不认为它曾经是 null。我把我的 listView 放在一个位置,使得我的 onFling 无法工作,但监听器仍然记录了一些东西...如果它是 null 的话,似乎 Log 会显示这个信息? - TheLettuceMaster
尝试使用速度极慢、难以判断是否为空的调试系统,并进行了最大努力。我使用了Log.d来查看何时调用了eventonFling,但有几次它们在我滑动时同时被调用,而Fragment并没有对Fling做出反应。这几乎表明我的Fling代码是错误的?但我相信所有的滑动都符合长度和速度的标准。 - TheLettuceMaster

1

首先

我注意到在你的手势监听器中,你在onDown中返回true。如果我正确理解了你的问题,你将点击和垂直滑动事件留给了ListView,因此你只对代码中的水平滑动事件感兴趣。

为什么不删除所有你不感兴趣的函数,这样就可以把一切你不感兴趣的交给ListView?如果你必须使用它们,至少返回默认值false(参见这里)。

另外,AbsListViewListView的父类)的代码显示出一个相当复杂的状态系统来处理点击和滑动,所以在我看来最好尽量不要去碰它。

其次

考虑到您最近的观察,也许关键是要考虑X和Y位移之间的比率,而不是滑动的绝对大小。虽然我无法解释滑动大小变化的原因,但我们真正感兴趣的是主要水平移动的运动。
(change in X) >> (change in Y)

这可以通过以下方式实现:

if ((velocityY == 0.0f) || (Math.abs(velocityX/velocityY) > 3.0f) {
    // The movement is predominantly horizontal
    // Put other checks here (like direction and so on)
    // Then do the stuff
}

这样做的好处是不使用e1e2,这就引出了...

第三点

正如您所指出的,问题出现在您与列表视图进行交互时。此时,可能会报告多个指针在Event中。不能保证它们返回的默认X和Y信息与任何特定的指针事件相关。如果您确实想要进行涉及X和Y的计算,请检查是否正在比较来自同一指针事件的信息,可以使用以下内容:

int numPointers = e1.getPointerCount();
for (int pointerIndex1 = 0; pointerIndex1 < numPointers; pointerIndex1++) {
    int pointerId = e1.getPointerId(pointerIndex1);
    // find index for pointerId in e2.

    float deltaX = e2.getX(pointerIndex2) - e1.getX(pointerIndex1);
    // and so on
}

在记录我的操作后,我发现了一些事情;当我在列表中向下滑动(并与之交互的次数越多)时,测量结果就不再一致。例如,如果我将手指向左或向右移动一厘米 - 在列表的顶部,它会显示我移动了200个像素。但是当我遇到上述问题时,它要低得多,可能只有50个像素,即使是相同的1厘米距离。因此,窗口没有关闭,因为它没有满足我的条件。 - TheLettuceMaster
嘿,我再次感谢您的反馈。我会看看我能想出什么,并随时向您报告。 - TheLettuceMaster

0

首先尝试像这样制作OnTouchListener

View.OnTouchListener gestureListener = new View.OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event) {
    gestureDetector.onTouchEvent(event);
        return true;
    }
};

那么 - 你总是在 onFling() 中返回 false。在 try/catch 子句中返回 true。并尝试在 onFling 中的 else 子句内将相同的数据传递给 onScroll 方法。


我一直对此有些困惑:返回 falsetrue 有什么不同?同时,将 onFling() 中的第一个 false 更改为 true 基本上会影响正常的 listView 滚动。它会让您滚动列表,但只会在您触摸时移动... - TheLettuceMaster
你必须在 onFling 中检测水平滑动并自己进行列表滚动。关于 true/false:当我按照文档所说的方式操作时,我的 gestureListener 没有起作用(我写得不太好)。所以我建议你按照我所做的去做。 - Lingviston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接