Преобразование GWT
Я работаю над Big Project, и у меня есть много кода GWT. Теперь я работаю над тем, чтобы проект полностью совместим с планшетами, такими как iPad и Android Tablets.
Как часть этого, я заметил, что сенсорные устройства задерживают 300 мс для обработки событий кликов. В этом проекте запись событий касания снова является очень утомительной задачей. Я провел много исследований в этом и нашел API Google Fast Buttons, используемый в приложении Google Voice. Я пробовал это и работаю хорошо, но требует много кодирования и JSNI.
Мой вопрос: есть ли что-нибудь еще в ваших знаниях, чтобы легко преодолеть эту задержку?
Ответы
Ответ 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