Подтвердить что ты не робот

Преобразование GWT

Я работаю над Big Project, и у меня есть много кода GWT. Теперь я работаю над тем, чтобы проект полностью совместим с планшетами, такими как iPad и Android Tablets.

Как часть этого, я заметил, что сенсорные устройства задерживают 300 мс для обработки событий кликов. В этом проекте запись событий касания снова является очень утомительной задачей. Я провел много исследований в этом и нашел API Google Fast Buttons, используемый в приложении Google Voice. Я пробовал это и работаю хорошо, но требует много кодирования и JSNI.

Мой вопрос: есть ли что-нибудь еще в ваших знаниях, чтобы легко преодолеть эту задержку?

4b9b3361

Ответ 1

Вот чистая реализация 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;
  }
}  

Ответ 2

Я попытался использовать приведенные выше ответы и комментарии, чтобы принять удар по этой реализации.

Я также разместил образец GWT-проекта, который можно использовать для простого сравнения:

http://gwt-fast-touch-press.appspot.com/

https://github.com/ashtonthomas/gwt-fast-touch-press

Обратите внимание, что вы сможете видеть только время, сохраненное, если вы находитесь на мобильном устройстве (или устройствах, которые обрабатывают события касания и не просто возвращаются к onClick).

Я добавил 3 быстрых кнопки и 3 обычных кнопки. Вы можете легко увидеть улучшение, когда на более старых мобильных устройствах, а иногда и на более новых (Samsung Galaxy Nexus показал только задержки около 100 мс, в то время как iPad первого поколения был более 400 мс почти каждый раз). Самое большое улучшение - когда вы пытаетесь быстро и последовательно щелкать по ящикам (на самом деле не кнопки здесь, но можно адаптировать)

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

}

Ответ 3

Я думаю, что код из предыдущего ответа, как написано, имеет несколько проблем, в частности, когда их несколько касаний.

(ПРИМЕЧАНИЕ. Я смотрю на код, который я написал, используя библиотеку Elemental в качестве ссылки, поэтому некоторые из вызовов могут отличаться в пользовательской библиотеке).

a) Код не фильтрует штрихи, направленные на кнопку; он вызывает TouchEvent.getTouches(). Вы хотите вызвать TouchEvent.getTargetTouches() на touchstart и touchmove, чтобы получить штрихи только для вашей кнопки. Вы хотите вызвать TouchEvent.getChangedTouches() на touchhend, чтобы получить конечное касание.

b) В коде не учитывается мультитач. В touchstart вы можете проверить, что доступно одно касание, и выйдите из него, если их больше одного. Кроме того, на touchstart отмените идентификатор касания, затем используйте его в touchmove и коснитесь, чтобы найти свой идентификатор касания в возвращаемом массиве (в случае, если пользователь коснулся другого пальца позже). Вы также можете упростить и проверить наличие нескольких касаний касания, а затем коснуться и подзаголовок.

c) Я считаю, что вам нужно вызвать stopPropagation на touchstart, так как вы обрабатываете событие. Я не вижу, где они называют event.stopPropagation на событии touchstart. Вы можете видеть, что это происходит в обработчиках кликов, но не в touchstart. Это предотвращает автоматическое изменение касания браузером, что приведет к нескольким щелчкам.

Существует и более простой способ. Если вы не заботитесь о перетаскивании, начиная с кнопки, вы можете просто вызвать свою логику щелчка в событии touchstart (и убедитесь, что вы проверяете одно касание и вызываете event.stopPropagation) и игнорируете touchmove и touchhend. Все материалы touchmove и touchhend предназначены для того, чтобы обрабатывать случай, позволяющий начать перетаскивание на кнопку.

Ответ 4

Также попробуйте FastClick

FastClick - простая, простая в использовании библиотека для устранения задержки 300 мс между физическим нажатием и стрельбой события кликов в мобильных браузерах. Цель состоит в том, чтобы сделать ваше приложение менее ленивым и более отзывчивым, избегая любых помех вашей текущей логике.

FastClick разработан FT Labs, частью Financial Times.

Библиотека была развернута как часть веб-приложения FT и проверена и протестирована в следующих мобильных браузерах:

  • Мобильное Safari на iOS 3 и выше
  • Chrome на iOS 5 и выше
  • Chrome на Android (ICS)
  • Opera Mobile 11.5 и выше
  • Android-браузер с Android 2
  • PlayBook OS 1 и выше

FastClick не подключает никаких прослушивателей на настольных браузерах, поскольку он не нужен. Те, которые были протестированы:

  • Safari
  • Chrome
  • Internet Explorer
  • Firefox
  • Opera