2012-03-07 11 views
12

Sto lavorando su un Big Project e ho scritto molto codice GWT. Ora sto lavorando per rendere il progetto completamente compatibile con tablet come iPad e tablet Android.Conversione di GWT Fare clic sugli eventi per toccare gli eventi

Come parte di questo, ho notato che i dispositivi touch prendono il ritardo 300 ms per gestire gli eventi click. In questo progetto, scrivere di nuovo gli eventi di tocco è un compito molto noioso. Ho fatto molte ricerche in questo e ho trovato l'API dei Google Fast Buttons utilizzata in Google Voice Application. L'ho provato e funziona bene, ma richiede molto codice e JSNI.

La mia domanda è, c'è qualcos'altro disponibile nella vostra conoscenza per superare facilmente questo ritardo?

+0

Bene sapere che questa discussione è ancora vivo ...: D –

risposta

14

Ecco un'implementazione Java puro del button.It veloce non include una sola riga di 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; 
    } 
} 
+0

Attualmente sto cercando di implementare alcuni parametri di compilazione per convertirlo. Ad esempio, utilizziamo il file modulo per definire il browser per rendere specifico il browser dell'app. Quindi se compilo l'applicazione con alcuni di questi parametri, dovrebbe funzionare per i browser mobili come iOS safari. –

+1

è possibile aggiungere l'agente utente nel module.xml e utilizzare il binding DEW GWT per selezionare dall'implementazione da pulsante a FastButton – sunnychayen

+0

hey @sunnychayen, questa soluzione non funziona se il gestore di clic del figlio aggiunge un nuovo elemento alla dom (come diciamo, un popup con autohide è impostato su true). l'evento click del browser avviene sul nuovo elemento e provoca: popup si apre -> il browser spara è clickevent -> c'è un clic sui confini esterni del nuovo popup -> nascondi popup. non è chiamato event.preventDefault(); –

6

penso che il codice dalla risposta precedente come scritto ha alcuni problemi, in particolare quando loro sono più tocchi.

(NOTA: sto osservando il codice che ho scritto usando la libreria Elemental come riferimento, quindi alcune chiamate potrebbero essere diverse nella libreria utente).

a) Il codice non sta filtrando i tocchi puntati sul pulsante; chiama TouchEvent.getTouches(). Vuoi chiamare TouchEvent.getTargetTouches() su touchstart e touchmove per ottenere il tocco giusto per il tuo pulsante. Vuoi chiamare TouchEvent.getChangedTouches() sul touchend per ottenere il tocco finale.

b) Il codice non tiene conto del multitouch. Al momento dell'avvio, è possibile verificare che sia disponibile un singolo tocco e salvare se ce n'è più di uno. Inoltre, all'avvio touchstash, nascondi l'id of touch, quindi utilizzalo in touchmove e touchend per trovare il tuo id touch nella matrice restituita (nel caso in cui l'utente abbia toccato un altro dito in seguito). Puoi anche semplificare e verificare più tocchi su touchmove e touchend e salvarli di nuovo.

c) Credo che sia necessario chiamare stopPropagation su touchstart, poiché si sta gestendo l'evento. Non vedo dove chiamano event.stopPropagation sull'evento touchstart È possibile vedere che ciò accade nei gestori di clic, ma non nel touchstart. Ciò impedisce al tocco di essere trasformato automaticamente in un clic dal browser, il che causerebbe più clic.

C'è anche un modo più semplice. Se non ti interessa trascinare l'avvio di un pulsante, puoi semplicemente chiamare la logica di clic nell'evento touchstart (e assicurarti di controllare il tocco singolo e chiamare event.stopPropagation) e ignorare touchmove e touchend. Tutto il touchmove e il touchend è gestire il caso di consentire il trascinamento per iniziare sul pulsante.

+0

in risposta al tuo ultimo commento, non vuoi che l'utente sia in grado di premere e tenere premuto il pulsante di destinazione indipendentemente dal fatto che l'utente A) lascia andare -> evento di fuoco B) pulsante di trascinamento -> nessun incendio? In tal caso, non si desidera chiamare la logica di clic all'avvio del tocco? –

+0

Vuoi dire evento di pressatura lunga ...? –

+1

Se si desidera mantenere premuto il pulsante o si desidera rifiutare un clic se l'utente si sposta dal pulsante, è necessario elaborare almeno l'evento di tocco e assicurarsi che termini sul pulsante. Quindi, sul touchstart, ricorda touch ID e on touchend, cambiaTouches e trova il tuo tocco con id e controlla se è all'interno del pulsante per determinare se il pulsante deve essere cliccato o meno. – Ezward

10

Ho provato a utilizzare le risposte e i commenti sopra riportati per prendere una piega in questa implementazione.

Ho anche posteds un progetto GWT di esempio che può essere utilizzato per un facile confronto:

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

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

prega di notare che vi sarà solo in grado di vedere il tempo risparmiato, se siete su un dispositivo mobile (o sui dispositivi che gestiscono gli eventi touch e non ricade semplicemente su onClick).

Ho aggiunto 3 pulsanti rapidi e 3 pulsanti normali. È possibile vedere facilmente un miglioramento quando si utilizzano dispositivi mobili meno recenti e talvolta meno recenti (il Samsung Galaxy Nexus mostrava solo ritardi di circa 100 ms mentre l'iPad di prima generazione superava i 400 ms quasi ogni volta). Il più grande miglioramento è quando si tenta di rapidamente e consecutivamente fare clic sulle caselle (non proprio pulsanti qui, ma può essere adattato) Prova

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

} 
+0

Questo è davvero buono ... Buono a vedere altri sviluppatori GWT stanno arrivando. Ho lasciato questo post una volta perché non sono riuscito a trovare altre soluzioni oltre a ciò che @sunnychayen ha fatto ....! –

+0

Avrò bisogno di aggiornare questo usando un metodo diverso per aggiungere un gestore diverso dal metodo astratto suTouchClickFire() e probabilmente dovrei semplicemente rinominare touchClick per "premere" –

+0

Ho aggiornato la mia risposta con un modo migliore per gestire ciò che è ora chiamato PressEvent (vedi git repo per tutti i file nel pacchetto: 'veloce') –

1

anche FastClick

FastClick è un semplice, facile da usare libreria per eliminare il ritardo di 300 ms tra un tocco fisico e l'attivazione di un evento click sui browser mobili. L'obiettivo è quello di rendere la tua applicazione meno rilassata e più reattiva evitando qualsiasi interferenza con la tua logica attuale.

FastClick è sviluppato da FT Labs, parte del Financial Times.

La biblioteca è stato schierato come parte del FT Web App ed è provato e testato sui seguenti browser mobili:

  • Safari Mobile su iOS 3 e verso l'alto
  • Chrome su iOS 5 e verso l'alto
  • Chrome su Android (ICS)
  • Opera mobile 11.5 e verso l'alto
  • Android Browser Android dal 2
  • PlayBoo k OS 1 e versioni successive

FastClick non associa alcun listener sul browser desktop in quanto non necessario. Quelli che sono stati testati sono:

  • Safari
  • Chrome
  • Internet Explorer
  • Firefox
  • Opera
+0

Questa sembra una buona soluzione, ma come possiamo implementarla in GWT? –

Problemi correlati