将GWT单击事件转换为触摸事件

将GWT单击事件转换为触摸事件,gwt,touch,tablet,Gwt,Touch,Tablet,我在一个大项目上工作,我写了很多GWT代码。现在我正致力于使该项目与iPad和Android平板电脑等平板电脑完全兼容 作为其中的一部分,我注意到触摸设备需要300ms延迟来处理点击事件。在这个项目中,再次编写触摸事件是一项非常乏味的任务。我在这方面做了很多研究,发现Google语音应用程序中使用了Google Fast Buttons API。我试过了,效果很好,但需要大量的编码和JSNI 我的问题是,根据您的知识,还有什么其他方法可以轻松克服这种延迟吗?这里是fast按钮的纯java实现。它

我在一个大项目上工作,我写了很多GWT代码。现在我正致力于使该项目与iPad和Android平板电脑等平板电脑完全兼容

作为其中的一部分,我注意到触摸设备需要300ms延迟来处理点击事件。在这个项目中,再次编写触摸事件是一项非常乏味的任务。我在这方面做了很多研究,发现Google语音应用程序中使用了Google Fast Buttons API。我试过了,效果很好,但需要大量的编码和JSNI


我的问题是,根据您的知识,还有什么其他方法可以轻松克服这种延迟吗?

这里是fast按钮的纯java实现。它不包括一行JNSI

package com.apollo.tabletization.shared.util;

import java.util.Date;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.HasAllTouchHandlers;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;

/** Implementation of Google FastButton {@link http://code.google.com/mobile/articles/fast_buttons.html} */
public class FastButton extends Composite {

  private boolean touchHandled = false;
  private boolean clickHandled = false;
  private boolean touchMoved = false;
  private int startY;
  private int startX;
  private int timeStart;

  public FastButton(Widget child) {
    // TODO - messages
    assert (child instanceof HasAllTouchHandlers) : "";
      assert (child instanceof HasClickHandlers) : "";
        initWidget(child);
        sinkEvents(Event.TOUCHEVENTS | Event.ONCLICK);
  }

  @Override
  public Widget getWidget() {
    return super.getWidget();
  }

  @Override
  public void onBrowserEvent(Event event) {
    timeStart = getUnixTimeStamp();
    switch (DOM.eventGetType(event)) {
      case Event.ONTOUCHSTART:
        {
          onTouchStart(event);
          break;
        }
      case Event.ONTOUCHEND:
        {
          onTouchEnd(event);
          break;
        }
      case Event.ONTOUCHMOVE:
        {
          onTouchMove(event);
          break;
        }
      case Event.ONCLICK:
        {
          onClick(event);
          return;
        }
    }

    super.onBrowserEvent(event);
  }

  private void onClick(Event event) {
    event.stopPropagation();

    int timeEnd = getUnixTimeStamp();
    if(touchHandled) {
      //Window.alert("click via touch: "+ this.toString() + "..." +timeStart+"---"+timeEnd);
      touchHandled = false;
      clickHandled = true;
      super.onBrowserEvent(event);
    }
    else {  
      if(clickHandled) {

        event.preventDefault();
      }
      else {
        clickHandled = false;
        //Window.alert("click nativo: "+ this.toString()+ "..." +(timeStart-timeEnd)+"==="+timeStart+"---"+timeEnd);
        super.onBrowserEvent(event);
      }
    }
  }

  private void onTouchEnd(Event event)  {
    if (!touchMoved) {
      touchHandled = true;
      fireClick();
    }
  }

  private void onTouchMove(Event event)  {
    if (!touchMoved) {
      Touch touch = event.getTouches().get(0);
      int deltaX = Math.abs(startX - touch.getClientX()); 
      int deltaY = Math.abs(startY - touch.getClientY());

      if (deltaX > 5 || deltaY > 5) {
        touchMoved = true;
      }
    }
  }

  private void onTouchStart(Event event) {
    Touch touch = event.getTouches().get(0);
    this.startX = touch.getClientX();
    this.startY = touch.getClientY();               
    touchMoved = false;
  }

  private void fireClick() {
    NativeEvent evt = Document.get().createClickEvent(1, 0, 0, 0, 0, false,
        false, false, false);
    getElement().dispatchEvent(evt);
  }

  private int getUnixTimeStamp() {
    Date date = new Date();
    int iTimeStamp = (int) (date.getTime() * .001);
    return iTimeStamp;
  }
}  

我认为前面的答案中的代码有一些问题,特别是当它们是多次接触时

(注意:我正在查看我使用Elemental库作为参考编写的代码,因此某些调用在用户库中可能会有所不同)

a) 代码未过滤针对按钮的触摸;它调用TouchEvent.getTouches()。您希望在touchstart和touchmove上调用TouchEvent.getTargetTouches(),以便仅为您的按钮进行触摸。您希望在touchend上调用TouchEvent.getChangedTouches()以获得结束触摸

b) 该代码不考虑多点触控。在touchstart上,您可以检查单个触摸是否可用,如果有多个触摸,则可以退出。此外,在touchstart上,隐藏touch的id,然后在touchmove和touchend中使用它在返回的阵列中查找您的touch id(以防用户稍后触摸到另一个手指)。您还可以简化并检查touchmove和touchend上的多个触摸,然后再次在那里进行切换

c) 我相信您需要在touchstart上调用stopPropagation,因为您正在处理该事件。我看不出他们在哪里调用event.stopPropagation。在touchstart事件中,您可以看到这种情况发生在单击处理程序中,但在touchstart中没有。这可以防止浏览器自动将触摸转换为单击,从而导致多次单击


还有一种更简单的方法。如果您不关心从按钮开始拖动,则只需在touchstart事件中调用您的单击逻辑(并确保您检查了单触,并调用event.stopPropagation),然后忽略touchmove和touchend。所有touchmove和touchend的内容都是为了处理允许在按钮上开始拖动的情况。

我尝试使用上述答案和注释来尝试此实现

我还发布了一个GWT项目示例,可用于方便比较:

请注意,只有在移动设备(或处理触摸事件的设备,而不仅仅是返回onClick)上,您才能看到节省的时间

我添加了3个快速按钮和3个普通按钮。在较旧的移动设备上,您可以很容易地看到改进,而在较新的移动设备上,有时改进更少(三星Galaxy Nexus仅显示约100ms的延迟,而第一代iPad几乎每次都超过400ms)。最大的改进是当您尝试快速、连续地单击框时(这里不是真正的按钮,但可以调整)

包io.ashton.fastpress.client.fast;
导入com.google.gwt.core.client.Scheduler;
导入com.google.gwt.core.client.Scheduler.RepeatingCommand;
导入com.google.gwt.core.client.ScheduledCommand;
导入com.google.gwt.dom.client.Touch;
导入com.google.gwt.event.shared.HandlerRegistration;
导入com.google.gwt.user.client.DOM;
导入com.google.gwt.user.client.Event;
导入com.google.gwt.user.client.ui.Composite;
导入com.google.gwt.user.client.ui.Widget;
/**
*
*受谷歌FastPressElement影响的GWT实施:
* https://developers.google.com/mobile/articles/fast_buttons
*
*使用以下代码示例和注释:
* http://stackoverflow.com/questions/9596807/converting-gwt-click-events-to-touch-events
*
*FastPressElement用于避免移动设备上的300毫秒延迟(仅在需要时执行此操作)
*为了忽略双击的可能性,浏览器会等待我们是否真的想点击
*双层(顶部)
*
*“按下”事件的发生速度将非常快(大约快300毫秒)。然而最大的
*改进来自于实现快速连续触摸。
*
*如果您尝试快速触摸一个或多个FastPress元素,您会发现一个非常好的效果
*改进。
*
*注意:不同的浏览器处理快速滑动或长时间按住/拖动触摸的方式不同。
*如果用户长按或拖动手指时按住,则为边缘情况
*稍微(但保持在元素上)-浏览器可能会触发事件,也可能不会触发事件。然而,
*浏览器总是会很快启动常规的点击/按下。
*
*TODO我们应该能够嵌入fastElements,并且使子fastElements不会在事件中冒泡
*因此,如果需要,我们可以嵌入元素(???)
*
*@作者阿什顿
*
*/
公共抽象类FastPressElement扩展了复合实现HasPressHandlers{
私有布尔touchHandled=false;
私有布尔clickHandled=false;
私有布尔值=false;
私有布尔值isEnabled=true;
私有int-touchId;
private int flashDelay=75;//默认时间延迟(毫秒)更改为flash样式
公共FastPressElement(){
//接收点击和触摸事件
//从那以后,我就不会再沉迷于鼠标事件了
//我认为我们不会有任何收获
sinkEvents(Event.ONCLICK | Event.TOUCHEVENTS);//Event.TOUCHEVENTS添加所有(开始、结束、,
//取消、更改)
}
公共FastPressElement(int msDelay){
这个();
如果(msDelay>=0){
flashDelay=msDelay;
}
}
已启用公共void集(已启用布尔值){
如果(已启用){
OneEnablePressStyle();
}埃尔斯
package io.ashton.fastpress.client.fast;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;

/**
 *
 * GWT Implementation influenced by Google's FastPressElement:
 * https://developers.google.com/mobile/articles/fast_buttons
 *
 * Using Code examples and comments from:
 * http://stackoverflow.com/questions/9596807/converting-gwt-click-events-to-touch-events
 *
 * The FastPressElement is used to avoid the 300ms delay on mobile devices (Only do this if you want
 * to ignore the possibility of a double tap - The browser waits to see if we actually want to
 * double top)
 *
 * The "press" event will occur significantly fast (around 300ms faster). However the biggest
 * improvement is from enabling fast consecutive touches.
 *
 * If you try to rapidly touch one or more FastPressElements, you will notice a MUCH great
 * improvement.
 *
 * NOTE: Different browsers will handle quick swipe or long hold/drag touches differently.
 * This is an edge case if the user is long pressing or pressing while dragging the finger
 * slightly (but staying on the element) - The browser may or may not fire the event. However,
 * the browser will always fire the regular tap/press very quickly.
 *
 * TODO We should be able to embed fastElements and have the child fastElements NOT bubble the event
 * So we can embed the elements if needed (???)
 *
 * @author ashton
 *
 */
public abstract class FastPressElement extends Composite implements HasPressHandlers {

  private boolean touchHandled = false;
  private boolean clickHandled = false;
  private boolean touchMoved = false;
  private boolean isEnabled = true;
  private int touchId;
  private int flashDelay = 75; // Default time delay in ms to flash style change

  public FastPressElement() {
    // Sink Click and Touch Events
    // I am not going to sink Mouse events since
    // I don't think we will gain anything

    sinkEvents(Event.ONCLICK | Event.TOUCHEVENTS); // Event.TOUCHEVENTS adds all (Start, End,
                                                   // Cancel, Change)

  }

  public FastPressElement(int msDelay) {
    this();
    if (msDelay >= 0) {
      flashDelay = msDelay;
    }
  }

  public void setEnabled(boolean enabled) {
    if (enabled) {
      onEnablePressStyle();
    } else {
      onDisablePressStyle();
    }
    this.isEnabled = enabled;
  }

  /**
   * Use this method in the same way you would use addClickHandler or addDomHandler
   *
   */
  @Override
  public HandlerRegistration addPressHandler(PressHandler handler) {
    // Use Widget's addHandler to ensureHandlers and add the type/return handler
    // We don't use addDom/BitlessHandlers since we aren't sinkEvents
    // We also aren't even dealing with a DomEvent
    return addHandler(handler, PressEvent.getType());
  }

  /**
   *
   * @param event
   */
  private void firePressEvent(Event event) {
    // This better verify a ClickEvent or TouchEndEvent
    // TODO might want to verify
    // (hitting issue with web.bindery vs g.gwt.user package diff)
    PressEvent pressEvent = new PressEvent(event);
    fireEvent(pressEvent);
  }

  /**
   * Implement the handler for pressing but NOT releasing the button. Normally you just want to show
   * some CSS style change to alert the user the element is active but not yet pressed
   *
   * ONLY FOR STYLE CHANGE - Will briefly be called onClick
   *
   * TIP: Don't make a dramatic style change. Take note that if a user is just trying to scroll, and
   * start on the element and then scrolls off, we may not want to distract them too much. If a user
   * does scroll off the element,
   *
   */
  public abstract void onHoldPressDownStyle();

  /**
   * Implement the handler for release of press. This should just be some CSS or Style change.
   *
   * ONLY FOR STYLE CHANGE - Will briefly be called onClick
   *
   * TIP: This should just go back to the normal style.
   */
  public abstract void onHoldPressOffStyle();

  /**
   * Change styling to disabled
   */
  public abstract void onDisablePressStyle();

  /**
   * Change styling to enabled
   *
   * TIP:
   */
  public abstract void onEnablePressStyle();

  @Override
  public Widget getWidget() {
    return super.getWidget();
  }

  @Override
  public void onBrowserEvent(Event event) {
    switch (DOM.eventGetType(event)) {
      case Event.ONTOUCHSTART: {
        if (isEnabled) {
          onTouchStart(event);
        }
        break;
      }
      case Event.ONTOUCHEND: {
        if (isEnabled) {
          onTouchEnd(event);
        }
        break;
      }
      case Event.ONTOUCHMOVE: {
        if (isEnabled) {
          onTouchMove(event);
        }
        break;
      }
      case Event.ONCLICK: {
        if (isEnabled) {
          onClick(event);
        }
        return;
      }
      default: {
        // Let parent handle event if not one of the above (?)
        super.onBrowserEvent(event);
      }
    }

  }

  private void onClick(Event event) {
    event.stopPropagation();

    if (touchHandled) {
      // if the touch is already handled, we are on a device
      // that supports touch (so you aren't in the desktop browser)

      touchHandled = false;// reset for next press
      clickHandled = true;//

      super.onBrowserEvent(event);

    } else {
      if (clickHandled) {
        // Not sure how this situation would occur
        // onClick being called twice..
        event.preventDefault();
      } else {
        // Press not handled yet

        // We still want to briefly fire the style change
        // To give good user feedback
        // Show HoldPress when possible
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
          @Override
          public void execute() {
            // Show hold press
            onHoldPressDownStyle();

            // Now schedule a delay (which will allow the actual
            // onTouchClickFire to executed
            Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
              @Override
              public boolean execute() {
                // Clear the style change
                onHoldPressOffStyle();
                return false;
              }
            }, flashDelay);
          }
        });

        clickHandled = false;
        firePressEvent(event);

      }
    }
  }

  private void onTouchStart(Event event) {

    onHoldPressDownStyle(); // Show style change

    // Stop the event from bubbling up
    event.stopPropagation();

    // Only handle if we have exactly one touch
    if (event.getTargetTouches().length() == 1) {
      Touch start = event.getTargetTouches().get(0);
      touchId = start.getIdentifier();
      touchMoved = false;
    }

  }

  /**
   * Check to see if the touch has moved off of the element.
   *
   * NOTE that in iOS the elasticScroll may make the touch/move cancel more difficult.
   *
   * @param event
   */
  private void onTouchMove(Event event) {

    if (!touchMoved) {
      Touch move = null;

      for (int i = 0; i < event.getChangedTouches().length(); i++) {
        if (event.getChangedTouches().get(i).getIdentifier() == touchId) {
          move = event.getChangedTouches().get(i);
        }
      }

      // Check to see if we moved off of the original element

      // Use Page coordinates since we compare with widget's absolute coordinates
      int yCord = move.getPageY();
      int xCord = move.getPageX();

      boolean yTop = getWidget().getAbsoluteTop() > yCord; // is y above element
      boolean yBottom = (getWidget().getAbsoluteTop() + getWidget().getOffsetHeight()) < yCord; // y
                                                                                                // below
      boolean xLeft = getWidget().getAbsoluteLeft() > xCord; // is x to the left of element
      boolean xRight = (getWidget().getAbsoluteLeft() + getWidget().getOffsetWidth()) < xCord; // x
                                                                                               // to
                                                                                               // the
                                                                                               // right
      if (yTop || yBottom || xLeft || xRight) {
        touchMoved = true;
        onHoldPressOffStyle();// Go back to normal style
      }

    }

  }

  private void onTouchEnd(Event event) {
    if (!touchMoved) {
      touchHandled = true;
      firePressEvent(event);
      event.preventDefault();
      onHoldPressOffStyle();// Change back the style
    }
  }

}