使用Rx解释TouchLocation流中的“双击”

时间:2011-07-16 02:00:29

标签: windows-phone-7 xna system.reactive

我有一个IObservable< TouchLocation>并且我试图从中获取手势。

能够拔出水龙头,但在双击时不能缠头。

TouchLocation具有位置和状态,因此通过基于两个标准聚合这些来获得Tap。

1)位置在任何方向上移动不超过10px(允许一些移动但不是阻力)

2)在“按下”状态的事件开始并继续,直到其中一个处于“已释放”状态

3)“释放”发生在“按下”的1秒内

现在我有一个IObservable< List< TouchLocation>> (聚合的结果)并且需要产生双击。

如何返回第一个手势(以产生初始点击),然后将第二个手势替换为双击手势,但仅在1秒计时器之前接收到第二个Tap。如果在计时器之后收到,那么它也应该是一个简单的Tap。

3 个答案:

答案 0 :(得分:3)

一个有趣的问题。

// Setup some parameters
var singleTapInterval = TimeSpan.FromSeconds(0.5);
var dblTapInterval = TimeSpan.FromSeconds(1);

// the source of touches
IObservable<TouchLocation> txs; 

// Get single taps
                               // 0, 1, but no more than 
                               // 2 touches in the timespan
IObservable<TouchLocation> taps = txs.Buffer(2,singleTapInterval) 
                                    .Where(touches => touches.Count == 2 &&
                                           touches[0].state == "Pressed" &&
                                           touches[1].state == "Released" &&
                                           distance(touches) < distanceThreshold)
                                    .Select(touches => touches[1]);

// now detect double taps
IObservable<TapKind> dbltaps = taps.Buffer(2,dblTapInterval)
                                   .Where(taps => taps.Count == 2)
                                   .Select(_ => TapKind.SingleTap);

// detect single taps - taps must be _slower_ than the dblTapInterval 
// for this to work
IObservable<TapKind> singletaps = taps.Throttle(dblTapInterval)
                                      .Select(_ => TapKind.SingleTap);

// compbine the two, so we see either tap condition
IObservable<TapKind> interestingTaps = dbltaps.Merge(singletaps);

答案 1 :(得分:2)

我认为我能给你的最佳答案是“不要那样做”。

XNA具有内置机制,可提供点按,双击和other gestures。这些与系统范围的手势行为相匹配,并且比实现您自己的手势识别器更为可取。

有关如何使用内置手势功能的说明,请参阅this document on MSDN。这基本上是设置TouchPanel.EnabledGestures,轮询IsGestureAvailable,然后调用ReadGesture

答案 2 :(得分:2)

回到起点并意识到我已经过度复杂了......感谢Scott帮助重置我的方法!

“target”是IObservable&lt; TouchLocation&gt; (在扩展方法中)。

此设置处理发送第一个事件(因此我们不会丢失第一个事件,而zip正在等待),忽略保持(点击超过1秒),用双击替换第二个事件(但仅限于它发生在水龙头的1秒内,并且覆盖了一个我之前没有意识到的新请求,这确保了双击仅跟随水龙头而不是另一个双击(4个水龙头产生了Tap | DTap | DTap | DTap,现在是Tap | DTap | Tap | DTap,这是正确的行为)

可能会将此简化一点,但这是预重构工作版本:

var grouped = (from t in target
                group t by t.Id into groups
                select groups);

var presses = (from g in grouped
                from t in g
                where t.State == TouchLocationState.Pressed
                select t).Timestamp();

var releases = (from g in grouped
                from t in g
                where t.State == TouchLocationState.Released
                select t).Timestamp();

var pressAndRelease = presses.Zip(releases, (press, release) =>
    {
        return new { Press = press, Release = release };
    })
    .Where(pr =>
        {
            var delta = (pr.Release.Timestamp - pr.Press.Timestamp).TotalSeconds;

            return delta < 1;
        })
    .Timestamp();

var zipped = pressAndRelease.Zip(pressAndRelease.Skip(1), (prev, cur) =>
    {
        return new { Previous = prev, Current = cur };
    });

pressAndRelease.TakeUntil(zipped).Subscribe(a =>
    {
        Debug.WriteLine("FIRST TAP!");
    });

var wasDoubleTap = false;
zipped.Subscribe(a =>
    {
        var delta = (a.Current.Timestamp - a.Previous.Timestamp).TotalSeconds;

        if (wasDoubleTap || delta > 1)
        {
            Debug.WriteLine("TAP");
            wasDoubleTap = false;
        }
        else
        {
            Debug.WriteLine("DOUBLE TAP!");
            wasDoubleTap = true;
        }
    });