区分onClick和onDoubleClick在同一元素上以在Dart中执行不同的操作

时间:2014-07-11 23:07:40

标签: dart dart-html

我想做不明智的事情,并在每个事件类型的同一元素上放置onClick和onDoubleClick,从而导致不同的操作。特别是在图像上,单击以前进到下一个图像,双击以切换全屏。

当然,我得到两次点击,然后双击(虽然我知道有些浏览器只会在双击之前点击一次)。

我原本想让自己轻松一点,将每个事件放入缓冲区(List) - 或者更确切地说将event.type字符串添加到列表中,然后在经过一段合适的时间后,说250或300毫秒检查缓冲区中的最后一项,如果双击则转到全屏,否则前进列表的长度。

我发现这个列表只有一个项目,我还没有弄清楚如何让计时器工作..

我的尝试中有一个:

void catchClickEvents(Event e) {
  var eventTypes = new List<String>();
  eventTypes.add(e.type);
  Duration duration = const Duration(milliseconds: 300);
  var timeout = new Timer(duration, () => processEvents(eventTypes));
}

void processEvents(List eTypes) {
  // just to see what is going on...
  print(eTypes);
}

这会导致此输出打印到控制台:

[click]
[click]
[dblclick]

而不是

[click, click, dblclick]

如果我放慢速度,那么在将这三种事件类型打印在一起之前会有明显的延迟

因此...

更大的问题是 “在单击和双击之间进行分离并为每种方法执行不同的操作有什么方法?”

其他问题是:

如何使用连续事件填充缓冲区(稍后将其清除) - 或者甚至如何使用Dart的事件流作为缓冲区......

在检查缓冲区内容之前,实际的超时方式是什么?

(我想最后一个问题是'我应该放弃努力并选择一套带有字形图标的传统按钮吗?')

3 个答案:

答案 0 :(得分:1)

您的页面应尽快对用户输入作出反应。如果您等待确认双击 - 您的onClick将变得不那么敏感。您可以通过在第一次单击后更改元素的可视状态(例如,播放动画)来隐藏问题,但这可能会使用户感到困惑。手持设备会变得更糟。此外,如果元素只对onClick事件做出反应,你可以“欺骗”并听取onmousedown - 它会使你的UI更具响应性。

除此之外,从客户端到客户端的双击可能会有明显不同的触发时间间隔,对于用户而言,双击某些内容并不直观。你将不得不用不必要的提示来膨胀你的界面。

编辑 添加了我的解决方案。它应该是相当可扩展的和未来证明。

import 'dart:html';
import 'dart:async';
import 'dart:math';

//enum. Kinda... https://code.google.com/p/dart/issues/detail?id=88
class UIAction {
  static const NEXT = const UIAction._(0);
  static const FULLSCREEN = const UIAction._(1);
  static const ERROR = const UIAction._(2);
  final int value;
  const UIAction._(this.value);
}

/**
 *[UIEventToUIAction] transforms UIEvents into corresponding  UIActions.
 */
class UIEventToUIAction implements StreamTransformer<UIEvent, UIAction> {

/**
 * I use "guesstimate" value for [doubleClickInterval] but you can calculate
 * comfortable value for the user from his/her previous activity.
 */
  final Duration doubleClickInterval = const Duration(milliseconds: 400);

  final StreamController<UIAction> st = new StreamController();

  Stream<UIAction> bind(Stream<UIEvent> originalStream) {
    int t1 = 0,
        t2 = 0;
    bool isOdd = true;
    Duration deltaTime;
    originalStream.timeout(doubleClickInterval, onTimeout: (_) {
      if ((deltaTime != null) && (deltaTime >= doubleClickInterval)) {
        st.add(UIAction.NEXT);
      }
    }).forEach((UIEvent uiEvent) {
      isOdd ? t1 = uiEvent.timeStamp : t2 = uiEvent.timeStamp;
      deltaTime = new Duration(milliseconds: (t1 - t2).abs());
      if (deltaTime < doubleClickInterval) st.add(UIAction.FULLSCREEN);
      isOdd = !isOdd;
    });
    return st.stream;
  }
}


void main() {

  //Eagerly perform time consuming tasks to decrease the input latency.
  Future NextImageLoaded;
  Future LargeImageLoaded;
  element.onMouseDown.forEach((_) {
    NextImageLoaded = asyncActionStub(
        "load next image. Returns completed future if already loaded");
    LargeImageLoaded = asyncActionStub(
        "load large version of active image to show in fullscreen mode."
            "Returns completed future if already loaded");
  });


  Stream<UIEvent> userInputs = element.onClick as Stream<UIEvent>;

  userInputs.transform(new UIEventToUIAction()).forEach((action) {
    switch (action) {
      case UIAction.FULLSCREEN:
        LargeImageLoaded.then( (_) =>asyncActionStub("fullscreen mode") )
        .then((_) => print("'full screen' finished"));
        break;
      case UIAction.NEXT:
        NextImageLoaded.then( (_) =>asyncActionStub("next image") )
        .then((_) => print("'next image' finished"));
        break;
      default:
        asyncActionStub("???");
    }
  });
}


final DivElement element = querySelector("#element");

final Random rng = new Random();
final Set performed = new Set();
/**
 *[asyncActionStub] Pretends to asynchronously do something usefull.
 * Also pretends to use cache.
 */
Future asyncActionStub(String msg) {
  if (performed.contains(msg)) {
    return new Future.delayed(const Duration(milliseconds: 0));
  }
  print(msg);
  return new Future.delayed(
      new Duration(milliseconds: rng.nextInt(300)),
      () => performed.add(msg));
}

答案 1 :(得分:1)

问题是你的变量不是全局的。

var eventTypes = new List<String>();

void catchClickEvents(Event e) {
  eventTypes.add(e.type);
  Duration duration = const Duration(milliseconds: 300);
  var timeout = new Timer(duration, () => processEvents(eventTypes));
}

void processEvents(List eTypes) {
  print(eTypes);
}

还有e.detail应该返回点击次数。如果您不需要Internet Explorer,则可以使用它。列表的问题在于它会增长并且永远不会被清除。

让我们考虑一下我们所知道的内容:您获得点击事件,并且在某些时候您可以进行双击。

您可以在此处使用点击计数器。 (或使用e.detail)跳过第二次点击事件。因此,您只有clickdblclick

现在,当您收到点击事件时,您可以像以前一样创建一个新计时器并运行点击操作。如果你得到dblclick事件,你只需运行你的动作。这可能是这样的:

DivElement div = querySelector('#div');
Timer timeout = null;
div.onClick.listen((MouseEvent e) {
  if(e.detail >= 2) {
    e.preventDefault();
  } else {
    if(timeout != null) {
      timeout.cancel();
    }

    timeout = new Timer(new Duration(milliseconds: 150), () => print('click'));
  }
});

div.onDoubleClick.listen((MouseEvent e) {
  if(timeout != null) {
    timeout.cancel();
    timeout = null;
  }
  print('dblclick');
});

这是适用于我的示例代码。如果你不能依赖e.detail只是我们一个计数器,并在点击事件后几毫秒后重置它。

我希望这会对你有所帮助。

问候,罗伯特

答案 2 :(得分:1)

我不确定IE是否仍然具有此处说明的事件序列(没有第二次点击事件) https://stackoverflow.com/a/5511527/217408

如果是,您可以使用略有偏差的Roberts解决方案:

library app_element;

import 'dart:html' as dom;
import 'dart:async' as async;

Duration dblClickDelay = new Duration(milliseconds: 500);
async.Timer clickTimer;

void clickHandler(dom.MouseEvent e, [bool forReal = false]) {
  if(clickTimer == null) {
    clickTimer = new async.Timer(dblClickDelay, () {
      clickHandler(e, true);
      clickTimer = null;
    });
  } else if(forReal){
    print('click');
  }
}

void dblClickHandler(dom.MouseEvent e) {
  if(clickTimer != null) {
    clickTimer.cancel();
    clickTimer = null;
  }
  print('doubleClick');
}

void main() {
  dom.querySelector('button')
    ..onClick.listen(clickHandler)
    ..onDoubleClick.listen(dblClickHandler);
}

或者只使用带有mouseUp事件的Roberts解决方案,而不是click事件。