Use a state machine in the PanZoomController to fix some weird touch behaviours.

This commit is contained in:
Doug Turner 2011-11-07 14:17:01 -08:00
parent 8e424a3459
commit 17659a2dbe
2 changed files with 121 additions and 48 deletions

View File

@ -452,13 +452,18 @@ public class GeckoAppShell
layerController.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View view, MotionEvent event) {
float origX = event.getX();
float origY = event.getY();
/* Transform the point to the layer offset. */
IntPoint eventPoint = new IntPoint((int)Math.round(event.getX()),
(int)Math.round(event.getY()));
IntPoint eventPoint = new IntPoint((int)Math.round(origX),
(int)Math.round(origY));
IntPoint geckoPoint = layerController.convertViewPointToLayerPoint(eventPoint);
event.setLocation(geckoPoint.x, geckoPoint.y);
GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
/* Restore the view coordinates in case the caller further processes this event */
event.setLocation(origX, origY);
return true;
}
});

View File

@ -83,29 +83,39 @@ public class PanZoomController
// The surface is snapping back into place after overscrolling.
private static final int FLING_STATE_SNAPPING = 1;
private boolean mTouchMoved, mStopped;
private long mLastTimestamp;
private Timer mFlingTimer;
private Axis mX, mY;
private float mInitialZoomSpan;
/* The span at the first zoom event (in unzoomed page coordinates). */
private IntPoint mInitialZoomFocus;
private float mInitialZoomSpan;
/* The zoom focus at the first zoom event (in unzoomed page coordinates). */
private boolean mTracking, mZooming;
private IntPoint mInitialZoomFocus;
private enum PanZoomState {
NOTHING, /* no touch-start events received */
FLING, /* all touches removed, but we're still scrolling page */
TOUCHING, /* one touch-start event received */
PANNING, /* touch-start followed by move */
PANNING_HOLD, /* in panning, but haven't moved. similar to TOUCHING but after starting a pan */
PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */
}
private PanZoomState mState;
public PanZoomController(LayerController controller) {
mController = controller;
mX = new Axis(); mY = new Axis();
mStopped = true;
mState = PanZoomState.NOTHING;
populatePositionAndLength();
}
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: return onTouchStart(event);
case MotionEvent.ACTION_MOVE: return onTouchMove(event);
case MotionEvent.ACTION_UP: return onTouchEnd(event);
case MotionEvent.ACTION_CANCEL: return onTouchCancel(event);
default: return false;
}
}
@ -121,7 +131,18 @@ public class PanZoomController
* a pan or a zoom is taking place.
*/
public boolean getRedrawHint() {
return mStopped && !mTracking && !mZooming;
switch (mState) {
case NOTHING:
case TOUCHING:
case PANNING_HOLD:
return true;
case FLING:
case PANNING:
case PINCHING:
return false;
}
Log.e(LOG_NAME, "Unhandled case " + mState + " in getRedrawHint");
return true;
}
/*
@ -129,52 +150,92 @@ public class PanZoomController
*/
private boolean onTouchStart(MotionEvent event) {
/* If there is more than one finger down, we bail out to avoid misinterpreting a zoom
* gesture as a pan gesture. */
if (event.getPointerCount() > 1 || mZooming) {
mZooming = true;
mTouchMoved = false;
mStopped = true;
switch (mState) {
case FLING:
if (mFlingTimer != null) {
mFlingTimer.cancel();
mFlingTimer = null;
}
// fall through
case NOTHING:
mState = PanZoomState.TOUCHING;
mX.touchPos = event.getX(0);
mY.touchPos = event.getY(0);
return false;
case TOUCHING:
case PANNING:
case PANNING_HOLD:
case PINCHING:
mState = PanZoomState.PINCHING;
return false;
}
mX.touchPos = event.getX(0); mY.touchPos = event.getY(0);
mTouchMoved = mStopped = false;
mTracking = true;
// TODO: Hold timeout
return true;
Log.e(LOG_NAME, "Unhandled case " + mState + " in onTouchStart");
return false;
}
private boolean onTouchMove(MotionEvent event) {
if (event.getPointerCount() > 1 || mZooming) {
mZooming = true;
mTouchMoved = false;
mStopped = true;
switch (mState) {
case NOTHING:
case FLING:
// should never happen
Log.e(LOG_NAME, "Received impossible touch move while in " + mState);
return false;
case TOUCHING:
mLastTimestamp = System.currentTimeMillis();
// fall through
case PANNING_HOLD:
mState = PanZoomState.PANNING;
// fall through
case PANNING:
track(event, System.currentTimeMillis());
return true;
case PINCHING:
// scale gesture listener will handle this
return false;
}
if (!mTouchMoved)
mLastTimestamp = System.currentTimeMillis();
mTouchMoved = true;
// TODO: Clear hold timeout
track(event, System.currentTimeMillis());
if (mFlingTimer != null) {
mFlingTimer.cancel();
mFlingTimer = null;
}
return true;
Log.e(LOG_NAME, "Unhandled case " + mState + " in onTouchMove");
return false;
}
private boolean onTouchEnd(MotionEvent event) {
if (mZooming)
mZooming = false;
switch (mState) {
case NOTHING:
case FLING:
// should never happen
Log.e(LOG_NAME, "Received impossible touch end while in " + mState);
return false;
case TOUCHING:
mState = PanZoomState.NOTHING;
// TODO: send click to gecko
return false;
case PANNING:
case PANNING_HOLD:
mState = PanZoomState.FLING;
fling(System.currentTimeMillis());
return true;
case PINCHING:
int points = event.getPointerCount();
if (points == 1) {
// last touch up
mState = PanZoomState.NOTHING;
} else if (points == 2) {
int pointRemovedIndex = event.getActionIndex();
int pointRemainingIndex = 1 - pointRemovedIndex; // kind of a hack
mState = PanZoomState.TOUCHING;
mX.touchPos = event.getX(pointRemainingIndex);
mY.touchPos = event.getY(pointRemainingIndex);
} else {
// still pinching, do nothing
}
return true;
}
Log.e(LOG_NAME, "Unhandled case " + mState + " in onTouchEnd");
return false;
}
mTracking = false;
fling(System.currentTimeMillis());
return true;
private boolean onTouchCancel(MotionEvent event) {
mState = PanZoomState.NOTHING;
return false;
}
private void track(MotionEvent event, long timestamp) {
@ -188,7 +249,8 @@ public class PanZoomController
float absVelocity = (float)Math.sqrt(mX.velocity * mX.velocity +
mY.velocity * mY.velocity);
mStopped = absVelocity < STOPPED_THRESHOLD;
if (absVelocity < STOPPED_THRESHOLD)
mState = PanZoomState.PANNING_HOLD;
mX.applyEdgeResistance(); mX.displace();
mY.applyEdgeResistance(); mY.displace();
@ -199,7 +261,7 @@ public class PanZoomController
long timeStep = timestamp - mLastTimestamp;
mLastTimestamp = timestamp;
if (mStopped)
if (mState != PanZoomState.FLING)
mX.velocity = mY.velocity = 0.0f;
mX.displace(); mY.displace();
@ -273,7 +335,7 @@ public class PanZoomController
}
private void stop() {
mStopped = true;
mState = PanZoomState.NOTHING;
if (mFlingTimer != null) {
mFlingTimer.cancel();
mFlingTimer = null;
@ -488,6 +550,7 @@ public class PanZoomController
*/
@Override
public boolean onScale(ScaleGestureDetector detector) {
mState = PanZoomState.PINCHING;
float newZoom = detector.getCurrentSpan() / mInitialZoomSpan;
IntSize screenSize = mController.getScreenSize();
@ -498,11 +561,13 @@ public class PanZoomController
mController.setVisibleRect((int)Math.round(x), (int)Math.round(y),
(int)Math.round(width), (int)Math.round(height));
mController.notifyLayerClientOfGeometryChange();
populatePositionAndLength();
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mState = PanZoomState.PINCHING;
IntRect initialZoomRect = (IntRect)mController.getVisibleRect().clone();
float initialZoom = mController.getZoomFactor();
@ -514,7 +579,10 @@ public class PanZoomController
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// TODO
mState = PanZoomState.PANNING_HOLD;
mLastTimestamp = System.currentTimeMillis();
mX.touchPos = detector.getFocusX();
mY.touchPos = detector.getFocusY();
}
@Override