blob: fb6f8f17e93b01c8802ca9c422e8767cf9382275 [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.touch.client;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.dom.client.PartialSupport;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.TouchCancelEvent;
import com.google.gwt.event.dom.client.TouchCancelHandler;
import com.google.gwt.event.dom.client.TouchEndEvent;
import com.google.gwt.event.dom.client.TouchEndHandler;
import com.google.gwt.event.dom.client.TouchEvent;
import com.google.gwt.event.dom.client.TouchMoveEvent;
import com.google.gwt.event.dom.client.TouchMoveHandler;
import com.google.gwt.event.dom.client.TouchStartEvent;
import com.google.gwt.event.dom.client.TouchStartHandler;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.touch.client.Momentum.State;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HasScrolling;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Adds touch based scrolling to a scroll panel.
*
* <p>
* Touch based scrolling is only supported on devices that support touch events
* and do not implement native touch based scrolling.
* </p>
*/
@PartialSupport
public class TouchScroller {
/**
* A point associated with a time.
*
* Visible for testing.
*/
static class TemporalPoint {
private Point point;
private double time;
public TemporalPoint() {
}
/**
* Construct a new {@link TemporalPoint} for the specified point and time.
*/
public TemporalPoint(Point point, double time) {
setTemporalPoint(point, time);
}
public Point getPoint() {
return point;
}
public double getTime() {
return time;
}
/**
* Update the point and time.
*
* @param point the new point
* @param time the new time
*/
public void setTemporalPoint(Point point, double time) {
this.point = point;
this.time = time;
}
}
/**
* The command used to apply momentum.
*/
private class MomentumCommand implements RepeatingCommand {
private final Duration duration = new Duration();
private final Point initialPosition = getWidgetScrollPosition();
private int lastElapsedMillis = 0;
private State state;
private HandlerRegistration windowResizeHandler;
/**
* Construct a {@link MomentumCommand}.
*
* @param endVelocity the final velocity of the user drag
*/
public MomentumCommand(Point endVelocity) {
state = momentum.createState(initialPosition, endVelocity);
/**
* If the user resizes the window (which happens on orientation change of
* a mobile device), cancel the momentum. The scrollable widget may be
* resized, which will cause its content to reflow and invalidates the
* current scrolling position.
*/
windowResizeHandler = Window.addResizeHandler(new ResizeHandler() {
public void onResize(ResizeEvent event) {
finish();
}
});
}
@Override
public boolean execute() {
/*
* Stop the command if another touch event starts or if momentum is
* disabled.
*/
if (this != momentumCommand) {
finish();
return false;
}
// Get the current position from the momentum.
int cumulativeElapsedMillis = duration.elapsedMillis();
state.setElapsedMillis(cumulativeElapsedMillis - lastElapsedMillis);
lastElapsedMillis = cumulativeElapsedMillis;
state.setCumulativeElapsedMillis(cumulativeElapsedMillis);
// Calculate the new state.
boolean notDone = momentum.updateState(state);
// Momentum is finished, so the user is free to click.
if (!notDone) {
finish();
}
/*
* Apply the new position. Even if there is no additional momentum, we
* want to respect the end position that the momentum returns.
*/
setWidgetScrollPosition(state.getPosition());
/*
* If the scroll position reached the min/max of the scrollable area,
* finish the momentum command.
*/
int hPos = (int) state.getPosition().getX();
int hMin = widget.getMinimumHorizontalScrollPosition();
int hMax = widget.getMaximumHorizontalScrollPosition();
int vMin = widget.getMinimumVerticalScrollPosition();
int vMax = widget.getMaximumVerticalScrollPosition();
int vPos = (int) state.getPosition().getY();
if ((vMax <= vPos || vMin >= vPos) && (hMax <= hPos || hMin >= hPos)) {
finish();
return false;
}
return notDone;
}
/**
* Finish and cleanup this momentum command.
*/
private void finish() {
if (windowResizeHandler != null) {
windowResizeHandler.removeHandler();
windowResizeHandler = null;
}
if (this == momentumCommand) {
momentumCommand = null;
}
}
}
/**
* The command used to remove touch points that occurred while the scrolling
* momentum is active. Only the touch points that occurred before
* TIME_THRESHOLD get to be removed.
*/
private class MomentumTouchRemovalCommand implements RepeatingCommand {
@Override
public boolean execute() {
double currentTime = Duration.currentTimeMillis();
Iterator<TemporalPoint> iter = touchPositionsDuringMomentum.iterator();
while (iter.hasNext()) {
TemporalPoint point = iter.next();
if (currentTime - point.getTime() >= TIME_THRESHOLD) {
iter.remove();
}
}
return !touchPositionsDuringMomentum.isEmpty();
}
}
/**
* The number of frames per second the animation should run at.
*/
private static final double FRAMES_PER_SECOND = 60;
/**
* The number of ms to wait during a drag before updating the reported start
* position of the drag.
*/
private static final double MAX_TRACKING_TIME = 200;
/**
* The number of ms to wait before putting a position on deck.
*/
private static final double MAX_TRACKING_TIME_ON_DECK = MAX_TRACKING_TIME / 2;
/**
* Minimum movement of touch required to be considered a drag.
*/
private static final double MIN_TRACKING_FOR_DRAG = 5;
/**
* The threshold for how close the time of a click event has to be to that of
* a touch event for us to consider those events to be related; that is,
* caused by the same user action (tap).
*/
private static final int TIME_THRESHOLD = 2500;
/**
* The threshold for how close the coordinates of a click event has to be to
* those of a touch event for us to consider those events to be related;
* that is, caused by the same user action (tap).
*/
private static final int DISTANCE_THRESHOLD = 25;
/**
* The number of milliseconds per animation frame.
*/
private static final int MS_PER_FRAME = (int) (1000 / FRAMES_PER_SECOND);
/**
* A cached boolean indicating whether or not touch scrolling is supported.
* Set to a non-null value the first time {@link #isSupported()} is called.
*/
private static Boolean isSupported;
/**
* Return a new {@link TouchScroller}.
*
* @return a new {@link TouchScroller} if supported, and null otherwise
*/
public static TouchScroller createIfSupported() {
return isSupported() ? new TouchScroller() : null;
}
/**
* Return a new {@link TouchScroller} that augments the specified scrollable
* widget if supported, and null otherwise.
*
* @param widget the scrollable widget
* @return a new {@link TouchScroller} if supported, and null otherwise
*/
public static TouchScroller createIfSupported(HasScrolling widget) {
TouchScroller scroller = createIfSupported();
if (scroller != null) {
scroller.setTargetWidget(widget);
}
return scroller;
}
/**
* Runtime check for whether touch scrolling is supported in this browser.
* Returns true if touch events are supported but touch based scrolling is not
* natively supported.
*
* @return true if touch scrolling is supported, false if not
*/
public static boolean isSupported() {
if (isSupported == null) {
/*
* Android 3.0 devices support touch scrolling natively.
*
* TODO(jlabanca): Find a more reliable way to detect if native touch
* scrolling is supported.
*/
isSupported = TouchEvent.isSupported() && !isAndroid3();
}
return isSupported;
}
/**
* Check if the user agent is android 3.0 or greater.
*
* @return true if android 3.0+
*
*/
private static native boolean isAndroid3() /*-{
var ua = navigator.userAgent.toLowerCase();
return /android ([3-9]+)\.([0-9]+)/.exec(ua) != null;
}-*/;
/**
* The registration for the preview handler used to bust click events.
*/
private HandlerRegistration bustClickHandlerReg;
/**
* The registration for the attach event handler.
*/
private HandlerRegistration attachHandlerReg;
/**
* A boolean indicating that we are in a drag sequence. Dragging occurs after
* the user moves beyond a threshold distance.
*/
private boolean dragging;
/**
* Registrations for the handlers added to the widget.
*/
private final List<HandlerRegistration> handlerRegs =
new ArrayList<HandlerRegistration>();
/**
* The last (most recent) touch position. We need to keep track of this when
* we handle touch move events because the Touch is already destroyed before
* the touch end event fires.
*/
private final TemporalPoint lastTouchPosition = new TemporalPoint();
/**
* The momentum that determines how the widget scrolls after the user
* completes a gesture. Can be null if momentum is not supported.
*/
private Momentum momentum;
/**
* The repeating command used to continue momentum after the gesture ends. The
* command is instantiated after the user finishes a drag sequence. A non null
* value indicates that momentum is occurring.
*/
private RepeatingCommand momentumCommand;
/**
* The coordinate of the most recent relevant touch event. For most drag
* sequences this will be the same as the startCoordinate. If the touch
* gesture changes direction significantly or pauses for a while this
* coordinate will be updated to the coordinate of the on deck touchmove
* event.
*/
private final TemporalPoint recentTouchPosition = new TemporalPoint();
/**
* If the gesture takes too long, we update the recentTouchPosition to the
* position on deck, which occurred halfway through the max tracking time. We
* do this so that we don't base the velocity on two touch events that
* occurred very close to each other at the end of a long gesture.
*/
private TemporalPoint recentTouchPositionOnDeck;
/**
* The coordinate of the most recent touch event that triggered scrolling.
*/
private TemporalPoint recentScrollTriggeringTouchPosition =
new TemporalPoint();
/**
* The coordinates of the touch positions while the momentum is active. The
* points stored in this array will be removed after TIME_THRESHOLD.
*/
private List<TemporalPoint> touchPositionsDuringMomentum =
new ArrayList<TemporalPoint>();
/**
* The repeating command used to remove the touch points that were added to
* touchPositionsDuringMomentum after TIME_THRESHOLD.
*/
private RepeatingCommand momentumTouchRemovalCommand =
new MomentumTouchRemovalCommand();
/**
* The position of the scrollable when the first touch occured.
*/
private Point startScrollPosition;
/**
* The position of the first touch.
*/
private Point startTouchPosition;
/**
* A boolean indicating that we are in a touch sequence.
*/
private boolean touching;
/**
* The widget being augmented.
*/
private HasScrolling widget;
/**
* Construct a new {@link TouchScroller}. This constructor should be called
* using the static method {@link #createIfSupported()}.
*
* @see #createIfSupported()
*/
protected TouchScroller() {
setMomentum(new DefaultMomentum());
}
/**
* Get the {@link Momentum} that controls scrolling after the user completes a
* gesture.
*
* @return the scrolling {@link Momentum}, or null if disabled
*/
public Momentum getMomentum() {
return momentum;
}
/**
* Get the target {@link HasScrolling} widget that this scroller affects.
*
* @return the target widget
*/
public HasScrolling getTargetWidget() {
return widget;
}
/**
* Set the {@link Momentum} that controls scrolling after the user completes a
* gesture.
*
* @param momentum the scrolling {@link Momentum}, or null to disable
*/
public void setMomentum(Momentum momentum) {
this.momentum = momentum;
if (momentum == null) {
// Cancel the current momentum.
momentumCommand = null;
}
}
/**
* Set the target {@link HasScrolling} widget that this scroller affects.
*
* @param widget the target widget, or null to disbale
*/
public void setTargetWidget(HasScrolling widget) {
if (this.widget == widget) {
return;
}
// Cancel drag and momentum.
cancelAll();
// Release the old widget.
for (HandlerRegistration reg : handlerRegs) {
reg.removeHandler();
}
handlerRegs.clear();
removeAttachHandler();
removeBustClickHandler();
// Attach to the new widget.
this.widget = widget;
if (widget != null) {
if (widget.asWidget().isAttached()) {
setupBustClickHandler();
}
// Make sure to add/remove the bust click handler if the widget is attached/detached.
attachHandlerReg = widget.asWidget().addAttachHandler(new AttachEvent.Handler() {
@Override
public void onAttachOrDetach(AttachEvent event) {
if (event.isAttached()) {
setupBustClickHandler();
} else {
removeBustClickHandler();
}
}
});
// Add touch start handler.
handlerRegs.add(widget.asWidget().addDomHandler(new TouchStartHandler() {
@Override
public void onTouchStart(TouchStartEvent event) {
TouchScroller.this.onTouchStart(event);
}
}, TouchStartEvent.getType()));
// Add touch move handler.
handlerRegs.add(widget.asWidget().addDomHandler(new TouchMoveHandler() {
@Override
public void onTouchMove(TouchMoveEvent event) {
TouchScroller.this.onTouchMove(event);
}
}, TouchMoveEvent.getType()));
// Add touch end handler.
handlerRegs.add(widget.asWidget().addDomHandler(new TouchEndHandler() {
@Override
public void onTouchEnd(TouchEndEvent event) {
TouchScroller.this.onTouchEnd(event);
}
}, TouchEndEvent.getType()));
// Add touch cancel handler.
handlerRegs.add(widget.asWidget().addDomHandler(new TouchCancelHandler() {
@Override
public void onTouchCancel(TouchCancelEvent event) {
TouchScroller.this.onTouchCancel(event);
}
}, TouchCancelEvent.getType()));
}
}
/**
* Get touch from event.
*
* @param event the event
* @return the touch object
*/
protected Touch getTouchFromEvent(TouchEvent<?> event) {
JsArray<Touch> touches = event.getTouches();
return (touches.length() > 0) ? touches.get(0) : null;
}
/**
* Called when the object's drag sequence is complete.
*
* @param event the touch event
*/
protected void onDragEnd(TouchEvent<?> event) {
// There is no momentum or it isn't supported.
if (momentum == null) {
return;
}
// Schedule the momentum.
Point endVelocity = calculateEndVelocity(recentTouchPosition, lastTouchPosition);
if (endVelocity != null) {
momentumCommand = new MomentumCommand(endVelocity);
Scheduler.get().scheduleFixedDelay(momentumCommand, MS_PER_FRAME);
}
}
/**
* Called when the object has been dragged to a new position.
*
* @param event the touch event
*/
protected void onDragMove(TouchEvent<?> event) {
/*
* Scroll to the new position. Touch scrolling moves in the same direction
* as the finger dragging, whereas scrolling is inverted with traditional
* scrollbars.
*/
Point diff = startTouchPosition.minus(lastTouchPosition.getPoint());
Point curScrollPosition = startScrollPosition.plus(diff);
setWidgetScrollPosition(curScrollPosition);
}
/**
* Called when the object has started dragging.
*
* @param event the touch event
*/
protected void onDragStart(TouchEvent<?> event) {
}
/**
* Called when the user cancels a touch. This can happen if the user touches
* the screen with too many fingers.
*
* @param event the touch event
*/
protected void onTouchCancel(TouchEvent<?> event) {
onTouchEnd(event);
}
/**
* Called when the user releases a touch.
*
* @param event the touch event
*/
protected void onTouchEnd(TouchEvent<?> event) {
// Ignore the touch if we didn't catch a touch start event.
if (!touching) {
return;
}
touching = false;
// Stop dragging.
if (dragging) {
dragging = false;
onDragEnd(event);
}
}
/**
* Called when the user moves a touch.
*
* @param event the touch event
*/
protected void onTouchMove(TouchEvent<?> event) {
// Ignore the touch if we never caught a touch start event.
if (!touching) {
return;
}
// Check if we should start dragging.
Touch touch = getTouchFromEvent(event);
Point touchPoint = new Point(touch.getPageX(), touch.getPageY());
double touchTime = Duration.currentTimeMillis();
lastTouchPosition.setTemporalPoint(touchPoint, touchTime);
if (!dragging) {
Point diff = touchPoint.minus(startTouchPosition);
double absDiffX = Math.abs(diff.getX());
double absDiffY = Math.abs(diff.getY());
if (absDiffX > MIN_TRACKING_FOR_DRAG || absDiffY > MIN_TRACKING_FOR_DRAG) {
recentScrollTriggeringTouchPosition.setTemporalPoint(
recentTouchPosition.getPoint(), recentTouchPosition.getTime());
/*
* Check if we should defer to native scrolling. If the scrollable
* widget is already scrolled as far as it will go, then we don't want
* to prevent scrolling of the document.
*
* We cannot prevent native scrolling in only one direction (ie. we
* cannot allow native horizontal scrolling but prevent native vertical
* scrolling), so we make a best guess based on the direction of the
* drag.
*/
if (absDiffX > absDiffY) {
/*
* The user scrolled primarily in the horizontal direction, so check
* if we should defer left/right scrolling to the document.
*/
int hPosition = widget.getHorizontalScrollPosition();
int hMin = widget.getMinimumHorizontalScrollPosition();
int hMax = widget.getMaximumHorizontalScrollPosition();
if (diff.getX() < 0 && hMax <= hPosition) {
// Already scrolled to the right.
cancelAll();
return;
} else if (diff.getX() > 0 && hMin >= hPosition) {
// Already scrolled to the left.
cancelAll();
return;
}
} else {
/*
* The user scrolled primarily in the vertical direction, so check if
* we should defer up/down scrolling to the document.
*/
int vPosition = widget.getVerticalScrollPosition();
int vMin = widget.getMinimumVerticalScrollPosition();
int vMax = widget.getMaximumVerticalScrollPosition();
if (diff.getY() < 0 && vMax <= vPosition) {
// Already scrolled to the bottom.
cancelAll();
return;
} else if (diff.getY() > 0 && vMin >= vPosition) {
// Already scrolled to the top.
cancelAll();
return;
}
}
// Start dragging.
dragging = true;
onDragStart(event);
}
}
// Prevent native document level scrolling.
event.preventDefault();
if (dragging) {
// Continue dragging.
onDragMove(event);
/*
* Update the recent position. This happens when they are dragging slowly.
* If they are dragging slowly then we should reset the start time and
* position to where they are now. This will be important during the drag
* end when we report to the draggable delegate what kind of drag just
* happened.
*/
double trackingTime = touchTime - recentTouchPosition.getTime();
if (trackingTime > MAX_TRACKING_TIME && recentTouchPositionOnDeck != null) {
// See comment below.
recentTouchPosition.setTemporalPoint(recentTouchPositionOnDeck.getPoint(),
recentTouchPositionOnDeck.getTime());
recentTouchPositionOnDeck = null;
} else if (trackingTime > MAX_TRACKING_TIME_ON_DECK && recentTouchPositionOnDeck == null) {
/*
* When we are halfway to the max tracking time, put the current touch
* on deck. When we switch the recent touch position, we use the on deck
* position. That prevents us from calculating the velocity from two
* points that are too close in time (or the same time).
*/
recentTouchPositionOnDeck = new TemporalPoint(touchPoint, touchTime);
}
}
}
/**
* Called when the user starts a touch.
*
* @param event the touch event
*/
protected void onTouchStart(TouchEvent<?> event) {
// Touch start event always precedes the click event. So this
// recentScrollTriggeringTouchPosition gets reset before a click event
// occurs. That's why we don't need to compare the time of the ghost click
// and the recentScrollTriggeringTouchPosition.
recentScrollTriggeringTouchPosition.setTemporalPoint(null, 0);
// Ignore the touch if there is already a touch happening.
if (touching) {
return;
}
// Record the starting touch position.
Touch touch = getTouchFromEvent(event);
startTouchPosition = new Point(touch.getPageX(), touch.getPageY());
double startTouchTime = Duration.currentTimeMillis();
recentTouchPosition.setTemporalPoint(startTouchPosition, startTouchTime);
lastTouchPosition.setTemporalPoint(startTouchPosition, startTouchTime);
recentTouchPositionOnDeck = null;
/**
* If the user touches the screen while the scrolling momentum is active,
* add the touch point to an array so that the later click events can be
* compared to the points in the array and busted if it shouldn't be
* considered as click.
*/
if (isMomentumActive()) {
touchPositionsDuringMomentum.add(
new TemporalPoint(startTouchPosition, startTouchTime));
Scheduler.get().scheduleFixedDelay(
momentumTouchRemovalCommand, TIME_THRESHOLD);
}
// Record the starting scroll position.
startScrollPosition = getWidgetScrollPosition();
cancelAll();
touching = true;
}
/**
* Calculate the end velocity. Visible for testing.
*
* @param from the starting point
* @param to the ending point
* @return the end velocity, or null if it cannot be calculated
*/
Point calculateEndVelocity(TemporalPoint from, TemporalPoint to) {
/*
* Calculate the time since the recent touch. The time can be zero if the
* user pauses for too long, which updates the recentTouchPosition, then
* lets go without moving again.
*/
double time = to.getTime() - from.getTime();
if (time <= 0) {
return null;
}
/*
* Calculate the end velocities. The velocity is inverted from the direction
* of the gesture.
*/
Point dist = from.getPoint().minus(to.getPoint());
return new Point(dist.getX() / time, dist.getY() / time);
}
/**
* Visible for testing.
*/
TemporalPoint getLastTouchPosition() {
return lastTouchPosition;
}
/**
* Visible for testing.
*/
TemporalPoint getRecentTouchPosition() {
return recentTouchPosition;
}
/**
* Visible for testing.
*/
boolean isDragging() {
return dragging;
}
/**
* Check if momentum is currently active. Visible for testing.
*
* @return true if active, false if not
*/
boolean isMomentumActive() {
return (momentumCommand != null);
}
/**
* Visible for testing.
*/
boolean isTouching() {
return touching;
}
/**
* Removes the attach handler. Visible for testing.
*/
void removeAttachHandler() {
if (attachHandlerReg != null) {
attachHandlerReg.removeHandler();
attachHandlerReg = null;
}
}
/**
* Removes the bust click handler. Visible for testing.
*/
void removeBustClickHandler() {
if (bustClickHandlerReg != null) {
bustClickHandlerReg.removeHandler();
bustClickHandlerReg = null;
}
}
/**
* Sets up the bust click handler. Visible for testing.
*/
void setupBustClickHandler() {
removeBustClickHandler();
bustClickHandlerReg = Event.addNativePreviewHandler(new NativePreviewHandler() {
@Override
public void onPreviewNativeEvent(NativePreviewEvent event) {
if (Event.ONCLICK == event.getTypeInt()) {
Point clickPoint = new Point(
event.getNativeEvent().getClientX(),
event.getNativeEvent().getClientY());
/*
* In mobile browsers, when the user touches on the screen, drag
* his finger across and take his finger off, the browser fires a
* click event at the position where the touch start happen. This
* click event is called "ghost click". This click is not intended by
* the user and thus needs to be busted.
* Also, while the panel is scrolling, if the user taps on the screen
* we want to stop the scrolling and bust the click event that's fired
* because the user probably didn't mean to click on something.
*/
if (isClickScrollTriggeringTouch(clickPoint) ||
isClickTouchPositionDuringMomentum(clickPoint)) {
event.cancel();
event.getNativeEvent().stopPropagation();
event.getNativeEvent().preventDefault();
}
}
}
});
}
/**
* Cancel all existing touch, drag, and momentum.
*/
private void cancelAll() {
touching = false;
dragging = false;
momentumCommand = null;
}
/**
* Get the scroll position of the widget.
*/
private Point getWidgetScrollPosition() {
return new Point(widget.getHorizontalScrollPosition(), widget.getVerticalScrollPosition());
}
/**
* Tests if the given points fall within the threshold from each other.
*
* @param point1 first point
* @param point2 second point
* @return true if the points fall within the threshold from each other
*/
private boolean hitTest(Point point1, Point point2) {
Point diff = point1.minus(point2);
double absDiffX = Math.abs(diff.getX());
double absDiffY = Math.abs(diff.getY());
return (absDiffX <= DISTANCE_THRESHOLD) && (absDiffY <= DISTANCE_THRESHOLD);
}
/**
* Checks if the click event is related to the touch event that has triggered
* scrolling. Such click events occur as a result of ghost click (i.e. click
* event that is fired at the touch start position after the touch end
* happens) and should be busted.
*
* @param clickPoint point of the click event
* @return true if clickPoint is the same as the recent scroll triggering
* touch
*/
private boolean isClickScrollTriggeringTouch(Point clickPoint) {
if (recentScrollTriggeringTouchPosition.getPoint() != null) {
return hitTest(
clickPoint, recentScrollTriggeringTouchPosition.getPoint());
}
return false;
}
/**
* Checks if the click event point is related to the touch positions
* during the recent momentum scrolling.
*
* @param clickPoint point of the click event
* @return true if clickPoint is the same as one of the touch positions during
* the recent momentum scrolling
*/
private boolean isClickTouchPositionDuringMomentum(Point clickPoint) {
double currentTime = Duration.currentTimeMillis();
boolean same = false;
for (TemporalPoint point : touchPositionsDuringMomentum) {
if (currentTime - point.getTime() <= TIME_THRESHOLD &&
hitTest(clickPoint, point.getPoint())) {
same = true;
break;
}
}
return same;
}
/**
* Sets the scroll position of the widget.
*
* @param position the new position
*/
private void setWidgetScrollPosition(Point position) {
widget.setHorizontalScrollPosition((int) position.getX());
widget.setVerticalScrollPosition((int) position.getY());
}
}