diff options
Diffstat (limited to 'android/experimental/LOAndroid/app/src/main/java/org/mozilla/gecko/gfx/JavaPanZoomController.java')
-rw-r--r-- | android/experimental/LOAndroid/app/src/main/java/org/mozilla/gecko/gfx/JavaPanZoomController.java | 1461 |
1 files changed, 0 insertions, 1461 deletions
diff --git a/android/experimental/LOAndroid/app/src/main/java/org/mozilla/gecko/gfx/JavaPanZoomController.java b/android/experimental/LOAndroid/app/src/main/java/org/mozilla/gecko/gfx/JavaPanZoomController.java deleted file mode 100644 index ac1bf0d3d451..000000000000 --- a/android/experimental/LOAndroid/app/src/main/java/org/mozilla/gecko/gfx/JavaPanZoomController.java +++ /dev/null @@ -1,1461 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.gfx; - -import org.libreoffice.LOKitShell; -//import org.mozilla.gecko.GeckoAppShell; -//import org.mozilla.gecko.GeckoEvent; -//import org.mozilla.gecko.PrefsHelper; -//import org.mozilla.gecko.Tab; -//import org.mozilla.gecko.Tabs; -import org.mozilla.gecko.ZoomConstraints; -import org.mozilla.gecko.util.EventDispatcher; -import org.mozilla.gecko.util.FloatUtils; -//import org.mozilla.gecko.util.GamepadUtils; -import org.mozilla.gecko.util.GeckoEventListener; -import org.mozilla.gecko.util.ThreadUtils; - -import org.json.JSONObject; - -import android.graphics.PointF; -import android.graphics.RectF; -import android.os.Build; -import android.util.FloatMath; -import android.util.Log; -import android.view.GestureDetector; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; - -/* - * Handles the kinetic scrolling and zooming physics for a layer controller. - * - * Many ideas are from Joe Hewitt's Scrollability: - * https://github.com/joehewitt/scrollability/ - */ -class JavaPanZoomController - extends GestureDetector.SimpleOnGestureListener - implements PanZoomController, SimpleScaleGestureDetector.SimpleScaleGestureListener, GeckoEventListener -{ - private static final String LOGTAG = "GeckoPanZoomController"; - - private static String MESSAGE_ZOOM_RECT = "Browser:ZoomToRect"; - private static String MESSAGE_ZOOM_PAGE = "Browser:ZoomToPageWidth"; - private static String MESSAGE_TOUCH_LISTENER = "Tab:HasTouchListener"; - - // Animation stops if the velocity is below this value when overscrolled or panning. - private static final float STOPPED_THRESHOLD = 4.0f; - - // Animation stops is the velocity is below this threshold when flinging. - private static final float FLING_STOPPED_THRESHOLD = 0.1f; - - // The distance the user has to pan before we recognize it as such (e.g. to avoid 1-pixel pans - // between the touch-down and touch-up of a click). In units of density-independent pixels. - public static final float PAN_THRESHOLD = 1/16f * LOKitShell.getDpi(); //GeckoAppShell.getDpi(); - - // Angle from axis within which we stay axis-locked - private static final double AXIS_LOCK_ANGLE = Math.PI / 6.0; // 30 degrees - - // Axis-lock breakout angle - private static final double AXIS_BREAKOUT_ANGLE = Math.PI / 8.0; - - // The distance the user has to pan before we consider breaking out of a locked axis - public static final float AXIS_BREAKOUT_THRESHOLD = 1/32f * LOKitShell.getDpi(); //GeckoAppShell.getDpi(); - - // The maximum amount we allow you to zoom into a page - private static final float MAX_ZOOM = 4.0f; - - // The maximum amount we would like to scroll with the mouse - private static final float MAX_SCROLL = 0.075f * LOKitShell.getDpi(); - - // The maximum zoom factor adjustment per frame of the AUTONAV animation - private static final float MAX_ZOOM_DELTA = 0.125f; - - // The duration of the bounce animation in ns - private static final int BOUNCE_ANIMATION_DURATION = 250000000; - - 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_LOCKED_X, /* touch-start followed by move (i.e. panning with axis lock) X axis */ - PANNING_LOCKED_Y, /* as above for Y axis */ - PANNING, /* panning without axis lock */ - PANNING_HOLD, /* in panning, but not moving. - * similar to TOUCHING but after starting a pan */ - PANNING_HOLD_LOCKED_X, /* like PANNING_HOLD, but axis lock still in effect for X axis */ - PANNING_HOLD_LOCKED_Y, /* as above but for Y axis */ - PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */ - ANIMATED_ZOOM, /* animated zoom to a new rect */ - BOUNCE, /* in a bounce animation */ - WAITING_LISTENERS, /* a state halfway between NOTHING and TOUCHING - the user has - put a finger down, but we don't yet know if a touch listener has - prevented the default actions yet. we still need to abort animations. */ - AUTONAV, /* We are scrolling using an AutonavRunnable animation. This is similar - to the FLING state except that it must be stopped manually by the code that - started it, and it's velocity can be updated while it's running. */ - } - - private enum AxisLockMode { - STANDARD, /* Default axis locking mode that doesn't break out until finger release */ - FREE, /* No locking at all */ - STICKY /* Break out with hysteresis so that it feels as free as possible whilst locking */ - } - - private final PanZoomTarget mTarget; - private final SubdocumentScrollHelper mSubscroller; - private final Axis mX; - private final Axis mY; - private final TouchEventHandler mTouchEventHandler; - private final EventDispatcher mEventDispatcher; - - /* The task that handles flings, autonav or bounces. */ - private PanZoomRenderTask mAnimationRenderTask; - /* The zoom focus at the first zoom event (in page coordinates). */ - private PointF mLastZoomFocus; - /* The time the last motion event took place. */ - private long mLastEventTime; - /* Current state the pan/zoom UI is in. */ - private PanZoomState mState; - /* The per-frame zoom delta for the currently-running AUTONAV animation. */ - private float mAutonavZoomDelta; - /* The user selected panning mode */ - private AxisLockMode mMode; - /* A medium-length tap/press is happening */ - private boolean mMediumPress; - /* Used to change the scrollY direction */ - private boolean mNegateWheelScrollY; - /* Whether the current event has been default-prevented. */ - private boolean mDefaultPrevented; - - // Handler to be notified when overscroll occurs - private Overscroll mOverscroll; - - public JavaPanZoomController(PanZoomTarget target, View view, EventDispatcher eventDispatcher) { - mTarget = target; - mSubscroller = new SubdocumentScrollHelper(eventDispatcher); - mX = new AxisX(mSubscroller); - mY = new AxisY(mSubscroller); - mTouchEventHandler = new TouchEventHandler(view.getContext(), view, this); - - checkMainThread(); - - setState(PanZoomState.NOTHING); - - mEventDispatcher = eventDispatcher; - registerEventListener(MESSAGE_ZOOM_RECT); - registerEventListener(MESSAGE_ZOOM_PAGE); - registerEventListener(MESSAGE_TOUCH_LISTENER); - - mMode = AxisLockMode.STANDARD; - - String[] prefs = { "ui.scrolling.axis_lock_mode", - "ui.scrolling.negate_wheel_scrollY", - "ui.scrolling.gamepad_dead_zone" }; - mNegateWheelScrollY = false; - - /*PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() { - @Override public void prefValue(String pref, String value) { - if (pref.equals("ui.scrolling.axis_lock_mode")) { - if (value.equals("standard")) { - mMode = AxisLockMode.STANDARD; - } else if (value.equals("free")) { - mMode = AxisLockMode.FREE; - } else { - mMode = AxisLockMode.STICKY; - } - } - } - - @Override public void prefValue(String pref, int value) { - if (pref.equals("ui.scrolling.gamepad_dead_zone")) { - GamepadUtils.overrideDeadZoneThreshold((float)value / 1000f); - } - } - - @Override public void prefValue(String pref, boolean value) { - if (pref.equals("ui.scrolling.negate_wheel_scrollY")) { - mNegateWheelScrollY = value; - } - } - - @Override - public boolean isObserver() { - return true; - } - });*/ - - Axis.initPrefs(); - } - - @Override - public void destroy() { - unregisterEventListener(MESSAGE_ZOOM_RECT); - unregisterEventListener(MESSAGE_ZOOM_PAGE); - unregisterEventListener(MESSAGE_TOUCH_LISTENER); - mSubscroller.destroy(); - mTouchEventHandler.destroy(); - } - - private final static float easeOut(float t) { - // ease-out approx. - // -(t-1)^2+1 - t = t-1; - return -t*t+1; - } - - private void registerEventListener(String event) { - mEventDispatcher.registerEventListener(event, this); - } - - private void unregisterEventListener(String event) { - mEventDispatcher.unregisterEventListener(event, this); - } - - private void setState(PanZoomState state) { - if (state != mState) { - //GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PanZoom:StateChange", state.toString())); - mState = state; - - // Let the target know we've finished with it (for now) - if (state == PanZoomState.NOTHING) { - mTarget.panZoomStopped(); - } - } - } - - private ImmutableViewportMetrics getMetrics() { - return mTarget.getViewportMetrics(); - } - - private void checkMainThread() { - if (!ThreadUtils.isOnUiThread()) { - // log with full stack trace - Log.e(LOGTAG, "Uh-oh, we're running on the wrong thread!", new Exception()); - } - } - - @Override - public void handleMessage(String event, JSONObject message) { - try { - if (MESSAGE_ZOOM_RECT.equals(event)) { - float x = (float)message.getDouble("x"); - float y = (float)message.getDouble("y"); - final RectF zoomRect = new RectF(x, y, - x + (float)message.getDouble("w"), - y + (float)message.getDouble("h")); - if (message.optBoolean("animate", true)) { - mTarget.post(new Runnable() { - @Override - public void run() { - animatedZoomTo(zoomRect); - } - }); - } else { - mTarget.setViewportMetrics(getMetricsToZoomTo(zoomRect)); - } - } else if (MESSAGE_ZOOM_PAGE.equals(event)) { - ImmutableViewportMetrics metrics = getMetrics(); - RectF cssPageRect = metrics.getCssPageRect(); - - RectF viewableRect = metrics.getCssViewport(); - float y = viewableRect.top; - // attempt to keep zoom keep focused on the center of the viewport - float newHeight = viewableRect.height() * cssPageRect.width() / viewableRect.width(); - float dh = viewableRect.height() - newHeight; // increase in the height - final RectF r = new RectF(0.0f, - y + dh/2, - cssPageRect.width(), - y + dh/2 + newHeight); - if (message.optBoolean("animate", true)) { - mTarget.post(new Runnable() { - @Override - public void run() { - animatedZoomTo(r); - } - }); - } else { - mTarget.setViewportMetrics(getMetricsToZoomTo(r)); - } - } else if (MESSAGE_TOUCH_LISTENER.equals(event)) { - /*int tabId = message.getInt("tabID"); - final Tab tab = Tabs.getInstance().getTab(tabId); - tab.setHasTouchListeners(true); - mTarget.post(new Runnable() { - @Override - public void run() { - if (Tabs.getInstance().isSelectedTab(tab)) - mTouchEventHandler.setWaitForTouchListeners(true); - } - });*/ - } - } catch (Exception e) { - Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); - } - } - - /** This function MUST be called on the UI thread */ - @Override - public boolean onKeyEvent(KeyEvent event) { - if (Build.VERSION.SDK_INT <= 11) { - return false; - } - - if ((event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD - && event.getAction() == KeyEvent.ACTION_DOWN) { - - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_ZOOM_IN: - return animatedScale(0.2f); - case KeyEvent.KEYCODE_ZOOM_OUT: - return animatedScale(-0.2f); - } - } - return false; - } - - /** This function MUST be called on the UI thread */ - @Override - public boolean onMotionEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT <= 11) { - return false; - } - - switch (event.getSource() & InputDevice.SOURCE_CLASS_MASK) { - case InputDevice.SOURCE_CLASS_POINTER: - switch (event.getAction() & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_SCROLL: return handlePointerScroll(event); - } - break; - case InputDevice.SOURCE_CLASS_JOYSTICK: - switch (event.getAction() & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_MOVE: return handleJoystickNav(event); - } - break; - } - return false; - } - - /** This function MUST be called on the UI thread */ - @Override - public boolean onTouchEvent(MotionEvent event) { - return mTouchEventHandler.handleEvent(event); - } - - boolean handleEvent(MotionEvent event, boolean defaultPrevented) { - mDefaultPrevented = defaultPrevented; - - switch (event.getAction() & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: return handleTouchStart(event); - case MotionEvent.ACTION_MOVE: return handleTouchMove(event); - case MotionEvent.ACTION_UP: return handleTouchEnd(event); - case MotionEvent.ACTION_CANCEL: return handleTouchCancel(event); - } - return false; - } - - /** This function MUST be called on the UI thread */ - @Override - public void notifyDefaultActionPrevented(boolean prevented) { - mTouchEventHandler.handleEventListenerAction(!prevented); - } - - /** This function must be called from the UI thread. */ - @Override - public void abortAnimation() { - checkMainThread(); - // this happens when gecko changes the viewport on us or if the device is rotated. - // if that's the case, abort any animation in progress and re-zoom so that the page - // snaps to edges. for other cases (where the user's finger(s) are down) don't do - // anything special. - switch (mState) { - case FLING: - mX.stopFling(); - mY.stopFling(); - // fall through - case BOUNCE: - case ANIMATED_ZOOM: - // the zoom that's in progress likely makes no sense any more (such as if - // the screen orientation changed) so abort it - setState(PanZoomState.NOTHING); - // fall through - case NOTHING: - // Don't do animations here; they're distracting and can cause flashes on page - // transitions. - synchronized (mTarget.getLock()) { - mTarget.setViewportMetrics(getValidViewportMetrics()); - mTarget.forceRedraw(null); - } - break; - } - } - - /** This function must be called on the UI thread. */ - public void startingNewEventBlock(MotionEvent event, boolean waitingForTouchListeners) { - checkMainThread(); - mSubscroller.cancel(); - if (waitingForTouchListeners && (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { - // this is the first touch point going down, so we enter the pending state - // seting the state will kill any animations in progress, possibly leaving - // the page in overscroll - setState(PanZoomState.WAITING_LISTENERS); - } - } - - /** This must be called on the UI thread. */ - @Override - public void pageRectUpdated() { - if (mState == PanZoomState.NOTHING) { - synchronized (mTarget.getLock()) { - ImmutableViewportMetrics validated = getValidViewportMetrics(); - if (!getMetrics().fuzzyEquals(validated)) { - // page size changed such that we are now in overscroll. snap to the - // the nearest valid viewport - mTarget.setViewportMetrics(validated); - } - } - } - } - - /* - * Panning/scrolling - */ - - private boolean handleTouchStart(MotionEvent event) { - // user is taking control of movement, so stop - // any auto-movement we have going - stopAnimationTask(); - - switch (mState) { - case ANIMATED_ZOOM: - // We just interrupted a double-tap animation, so force a redraw in - // case this touchstart is just a tap that doesn't end up triggering - // a redraw - mTarget.forceRedraw(null); - // fall through - case FLING: - case AUTONAV: - case BOUNCE: - case NOTHING: - case WAITING_LISTENERS: - startTouch(event.getX(0), event.getY(0), event.getEventTime()); - return false; - case TOUCHING: - case PANNING: - case PANNING_LOCKED_X: - case PANNING_LOCKED_Y: - case PANNING_HOLD: - case PANNING_HOLD_LOCKED_X: - case PANNING_HOLD_LOCKED_Y: - case PINCHING: - Log.e(LOGTAG, "Received impossible touch down while in " + mState); - return false; - } - Log.e(LOGTAG, "Unhandled case " + mState + " in handleTouchStart"); - return false; - } - - private boolean handleTouchMove(MotionEvent event) { - - switch (mState) { - case FLING: - case AUTONAV: - case BOUNCE: - case WAITING_LISTENERS: - // should never happen - Log.e(LOGTAG, "Received impossible touch move while in " + mState); - // fall through - case ANIMATED_ZOOM: - case NOTHING: - // may happen if user double-taps and drags without lifting after the - // second tap. ignore the move if this happens. - return false; - - case TOUCHING: - // Don't allow panning if there is an element in full-screen mode. See bug 775511. - if ((mTarget.isFullScreen() && !mSubscroller.scrolling()) || panDistance(event) < PAN_THRESHOLD) { - return false; - } - cancelTouch(); - startPanning(event.getX(0), event.getY(0), event.getEventTime()); - track(event); - return true; - - case PANNING_HOLD_LOCKED_X: - setState(PanZoomState.PANNING_LOCKED_X); - track(event); - return true; - case PANNING_HOLD_LOCKED_Y: - setState(PanZoomState.PANNING_LOCKED_Y); - // fall through - case PANNING_LOCKED_X: - case PANNING_LOCKED_Y: - track(event); - return true; - - case PANNING_HOLD: - setState(PanZoomState.PANNING); - // fall through - case PANNING: - track(event); - return true; - - case PINCHING: - // scale gesture listener will handle this - return false; - } - Log.e(LOGTAG, "Unhandled case " + mState + " in handleTouchMove"); - return false; - } - - private boolean handleTouchEnd(MotionEvent event) { - - switch (mState) { - case FLING: - case AUTONAV: - case BOUNCE: - case ANIMATED_ZOOM: - case NOTHING: - // may happen if user double-taps and drags without lifting after the - // second tap. ignore if this happens. - return false; - - case WAITING_LISTENERS: - if (!mDefaultPrevented) { - // should never happen - Log.e(LOGTAG, "Received impossible touch end while in " + mState); - } - // fall through - case TOUCHING: - // the switch into TOUCHING might have happened while the page was - // snapping back after overscroll. we need to finish the snap if that - // was the case - bounce(); - return false; - - case PANNING: - case PANNING_LOCKED_X: - case PANNING_LOCKED_Y: - case PANNING_HOLD: - case PANNING_HOLD_LOCKED_X: - case PANNING_HOLD_LOCKED_Y: - setState(PanZoomState.FLING); - fling(); - return true; - - case PINCHING: - setState(PanZoomState.NOTHING); - return true; - } - Log.e(LOGTAG, "Unhandled case " + mState + " in handleTouchEnd"); - return false; - } - - private boolean handleTouchCancel(MotionEvent event) { - cancelTouch(); - - // ensure we snap back if we're overscrolled - bounce(); - return false; - } - - private boolean handlePointerScroll(MotionEvent event) { - if (mState == PanZoomState.NOTHING || mState == PanZoomState.FLING) { - float scrollX = event.getAxisValue(MotionEvent.AXIS_HSCROLL); - float scrollY = event.getAxisValue(MotionEvent.AXIS_VSCROLL); - if (mNegateWheelScrollY) { - scrollY *= -1.0; - } - scrollBy(scrollX * MAX_SCROLL, scrollY * MAX_SCROLL); - bounce(); - return true; - } - return false; - } - - private float filterDeadZone(MotionEvent event, int axis) { - return 0; //(GamepadUtils.isValueInDeadZone(event, axis) ? 0 : event.getAxisValue(axis)); - } - - private float normalizeJoystickScroll(MotionEvent event, int axis) { - return filterDeadZone(event, axis) * MAX_SCROLL; - } - - private float normalizeJoystickZoom(MotionEvent event, int axis) { - // negate MAX_ZOOM_DELTA so that pushing up on the stick zooms in - return filterDeadZone(event, axis) * -MAX_ZOOM_DELTA; - } - - // Since this event is a position-based event rather than a motion-based event, we need to - // set up an AUTONAV animation to keep scrolling even while we don't get events. - private boolean handleJoystickNav(MotionEvent event) { - float velocityX = normalizeJoystickScroll(event, MotionEvent.AXIS_X); - float velocityY = normalizeJoystickScroll(event, MotionEvent.AXIS_Y); - float zoomDelta = normalizeJoystickZoom(event, MotionEvent.AXIS_RZ); - - if (velocityX == 0 && velocityY == 0 && zoomDelta == 0) { - if (mState == PanZoomState.AUTONAV) { - bounce(); // if not needed, this will automatically go to state NOTHING - return true; - } - return false; - } - - if (mState == PanZoomState.NOTHING) { - setState(PanZoomState.AUTONAV); - startAnimationRenderTask(new AutonavRenderTask()); - } - if (mState == PanZoomState.AUTONAV) { - mX.setAutoscrollVelocity(velocityX); - mY.setAutoscrollVelocity(velocityY); - mAutonavZoomDelta = zoomDelta; - return true; - } - return false; - } - - private void startTouch(float x, float y, long time) { - mX.startTouch(x); - mY.startTouch(y); - setState(PanZoomState.TOUCHING); - mLastEventTime = time; - } - - private void startPanning(float x, float y, long time) { - float dx = mX.panDistance(x); - float dy = mY.panDistance(y); - double angle = Math.atan2(dy, dx); // range [-pi, pi] - angle = Math.abs(angle); // range [0, pi] - - // When the touch move breaks through the pan threshold, reposition the touch down origin - // so the page won't jump when we start panning. - mX.startTouch(x); - mY.startTouch(y); - mLastEventTime = time; - - if (mMode == AxisLockMode.STANDARD || mMode == AxisLockMode.STICKY) { - if (!mX.scrollable() || !mY.scrollable()) { - setState(PanZoomState.PANNING); - } else if (angle < AXIS_LOCK_ANGLE || angle > (Math.PI - AXIS_LOCK_ANGLE)) { - mY.setScrollingDisabled(true); - setState(PanZoomState.PANNING_LOCKED_X); - } else if (Math.abs(angle - (Math.PI / 2)) < AXIS_LOCK_ANGLE) { - mX.setScrollingDisabled(true); - setState(PanZoomState.PANNING_LOCKED_Y); - } else { - setState(PanZoomState.PANNING); - } - } else if (mMode == AxisLockMode.FREE) { - setState(PanZoomState.PANNING); - } - } - - private float panDistance(MotionEvent move) { - float dx = mX.panDistance(move.getX(0)); - float dy = mY.panDistance(move.getY(0)); - return FloatMath.sqrt(dx * dx + dy * dy); - } - - private void track(float x, float y, long time) { - float timeDelta = (float)(time - mLastEventTime); - if (FloatUtils.fuzzyEquals(timeDelta, 0)) { - // probably a duplicate event, ignore it. using a zero timeDelta will mess - // up our velocity - return; - } - mLastEventTime = time; - - - // if we're axis-locked check if the user is trying to scroll away from the lock - if (mMode == AxisLockMode.STICKY) { - float dx = mX.panDistance(x); - float dy = mY.panDistance(y); - double angle = Math.atan2(dy, dx); // range [-pi, pi] - angle = Math.abs(angle); // range [0, pi] - - if (Math.abs(dx) > AXIS_BREAKOUT_THRESHOLD || Math.abs(dy) > AXIS_BREAKOUT_THRESHOLD) { - if (mState == PanZoomState.PANNING_LOCKED_X) { - if (angle > AXIS_BREAKOUT_ANGLE && angle < (Math.PI - AXIS_BREAKOUT_ANGLE)) { - mY.setScrollingDisabled(false); - setState(PanZoomState.PANNING); - } - } else if (mState == PanZoomState.PANNING_LOCKED_Y) { - if (Math.abs(angle - (Math.PI / 2)) > AXIS_BREAKOUT_ANGLE) { - mX.setScrollingDisabled(false); - setState(PanZoomState.PANNING); - } - } - } - } - - mX.updateWithTouchAt(x, timeDelta); - mY.updateWithTouchAt(y, timeDelta); - } - - private void track(MotionEvent event) { - mX.saveTouchPos(); - mY.saveTouchPos(); - - for (int i = 0; i < event.getHistorySize(); i++) { - track(event.getHistoricalX(0, i), - event.getHistoricalY(0, i), - event.getHistoricalEventTime(i)); - } - track(event.getX(0), event.getY(0), event.getEventTime()); - - if (stopped()) { - if (mState == PanZoomState.PANNING) { - setState(PanZoomState.PANNING_HOLD); - } else if (mState == PanZoomState.PANNING_LOCKED_X) { - setState(PanZoomState.PANNING_HOLD_LOCKED_X); - } else if (mState == PanZoomState.PANNING_LOCKED_Y) { - setState(PanZoomState.PANNING_HOLD_LOCKED_Y); - } else { - // should never happen, but handle anyway for robustness - Log.e(LOGTAG, "Impossible case " + mState + " when stopped in track"); - setState(PanZoomState.PANNING_HOLD); - } - } - - mX.startPan(); - mY.startPan(); - updatePosition(); - } - - private void scrollBy(float dx, float dy) { - mTarget.scrollBy(dx, dy); - } - - private void fling() { - updatePosition(); - - stopAnimationTask(); - - boolean stopped = stopped(); - mX.startFling(stopped); - mY.startFling(stopped); - - startAnimationRenderTask(new FlingRenderTask()); - } - - /* Performs a bounce-back animation to the given viewport metrics. */ - private void bounce(ImmutableViewportMetrics metrics, PanZoomState state) { - stopAnimationTask(); - - ImmutableViewportMetrics bounceStartMetrics = getMetrics(); - if (bounceStartMetrics.fuzzyEquals(metrics)) { - setState(PanZoomState.NOTHING); - return; - } - - setState(state); - - // At this point we have already set mState to BOUNCE or ANIMATED_ZOOM, so - // getRedrawHint() is returning false. This means we can safely call - // setAnimationTarget to set the new final display port and not have it get - // clobbered by display ports from intermediate animation frames. - mTarget.setAnimationTarget(metrics); - startAnimationRenderTask(new BounceRenderTask(bounceStartMetrics, metrics)); - } - - /* Performs a bounce-back animation to the nearest valid viewport metrics. */ - private void bounce() { - bounce(getValidViewportMetrics(), PanZoomState.BOUNCE); - } - - /* Starts the fling or bounce animation. */ - private void startAnimationRenderTask(final PanZoomRenderTask task) { - if (mAnimationRenderTask != null) { - Log.e(LOGTAG, "Attempted to start a new task without canceling the old one!"); - stopAnimationTask(); - } - - mAnimationRenderTask = task; - mTarget.postRenderTask(mAnimationRenderTask); - } - - /* Stops the fling or bounce animation. */ - private void stopAnimationTask() { - if (mAnimationRenderTask != null) { - mAnimationRenderTask.terminate(); - mTarget.removeRenderTask(mAnimationRenderTask); - mAnimationRenderTask = null; - } - } - - private float getVelocity() { - float xvel = mX.getRealVelocity(); - float yvel = mY.getRealVelocity(); - return FloatMath.sqrt(xvel * xvel + yvel * yvel); - } - - @Override - public PointF getVelocityVector() { - return new PointF(mX.getRealVelocity(), mY.getRealVelocity()); - } - - private boolean stopped() { - return getVelocity() < STOPPED_THRESHOLD; - } - - PointF resetDisplacement() { - return new PointF(mX.resetDisplacement(), mY.resetDisplacement()); - } - - private void updatePosition() { - mX.displace(); - mY.displace(); - PointF displacement = resetDisplacement(); - if (FloatUtils.fuzzyEquals(displacement.x, 0.0f) && FloatUtils.fuzzyEquals(displacement.y, 0.0f)) { - return; - } - if (mDefaultPrevented || mSubscroller.scrollBy(displacement)) { - synchronized (mTarget.getLock()) { - mTarget.scrollMarginsBy(displacement.x, displacement.y); - } - } else { - synchronized (mTarget.getLock()) { - scrollBy(displacement.x, displacement.y); - } - } - } - - /** - * This class is an implementation of RenderTask which enforces its implementor to run in the UI thread. - * - */ - private abstract class PanZoomRenderTask extends RenderTask { - - /** - * the time when the current frame was started in ns. - */ - protected long mCurrentFrameStartTime; - /** - * The current frame duration in ns. - */ - protected long mLastFrameTimeDelta; - - private final Runnable mRunnable = new Runnable() { - @Override - public final void run() { - if (mContinueAnimation) { - animateFrame(); - } - } - }; - - private boolean mContinueAnimation = true; - - public PanZoomRenderTask() { - super(false); - } - - @Override - protected final boolean internalRun(long timeDelta, long currentFrameStartTime) { - - mCurrentFrameStartTime = currentFrameStartTime; - mLastFrameTimeDelta = timeDelta; - - mTarget.post(mRunnable); - return mContinueAnimation; - } - - /** - * The method subclasses must override. This method is run on the UI thread thanks to internalRun - */ - protected abstract void animateFrame(); - - /** - * Terminate the animation. - */ - public void terminate() { - mContinueAnimation = false; - } - } - - private class AutonavRenderTask extends PanZoomRenderTask { - public AutonavRenderTask() { - super(); - } - - @Override - protected void animateFrame() { - if (mState != PanZoomState.AUTONAV) { - finishAnimation(); - return; - } - - updatePosition(); - synchronized (mTarget.getLock()) { - mTarget.setViewportMetrics(applyZoomDelta(getMetrics(), mAutonavZoomDelta)); - } - } - } - - /* The task that performs the bounce animation. */ - private class BounceRenderTask extends PanZoomRenderTask { - - /* - * The viewport metrics that represent the start and end of the bounce-back animation, - * respectively. - */ - private ImmutableViewportMetrics mBounceStartMetrics; - private ImmutableViewportMetrics mBounceEndMetrics; - // How long ago this bounce was started in ns. - private long mBounceDuration; - - BounceRenderTask(ImmutableViewportMetrics startMetrics, ImmutableViewportMetrics endMetrics) { - super(); - mBounceStartMetrics = startMetrics; - mBounceEndMetrics = endMetrics; - } - - @Override - protected void animateFrame() { - /* - * The pan/zoom controller might have signaled to us that it wants to abort the - * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail - * out. - */ - if (!(mState == PanZoomState.BOUNCE || mState == PanZoomState.ANIMATED_ZOOM)) { - finishAnimation(); - return; - } - - /* Perform the next frame of the bounce-back animation. */ - mBounceDuration = mCurrentFrameStartTime - getStartTime(); - if (mBounceDuration < BOUNCE_ANIMATION_DURATION) { - advanceBounce(); - return; - } - - /* Finally, if there's nothing else to do, complete the animation and go to sleep. */ - finishBounce(); - finishAnimation(); - setState(PanZoomState.NOTHING); - } - - /* Performs one frame of a bounce animation. */ - private void advanceBounce() { - synchronized (mTarget.getLock()) { - float t = easeOut((float)mBounceDuration / BOUNCE_ANIMATION_DURATION); - ImmutableViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t); - mTarget.setViewportMetrics(newMetrics); - } - } - - /* Concludes a bounce animation and snaps the viewport into place. */ - private void finishBounce() { - synchronized (mTarget.getLock()) { - mTarget.setViewportMetrics(mBounceEndMetrics); - } - } - } - - // The callback that performs the fling animation. - private class FlingRenderTask extends PanZoomRenderTask { - - public FlingRenderTask() { - super(); - } - - @Override - protected void animateFrame() { - /* - * The pan/zoom controller might have signaled to us that it wants to abort the - * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail - * out. - */ - if (mState != PanZoomState.FLING) { - finishAnimation(); - return; - } - - /* Advance flings, if necessary. */ - boolean flingingX = mX.advanceFling(mLastFrameTimeDelta); - boolean flingingY = mY.advanceFling(mLastFrameTimeDelta); - - boolean overscrolled = (mX.overscrolled() || mY.overscrolled()); - - /* If we're still flinging in any direction, update the origin. */ - if (flingingX || flingingY) { - updatePosition(); - - /* - * Check to see if we're still flinging with an appreciable velocity. The threshold is - * higher in the case of overscroll, so we bounce back eagerly when overscrolling but - * coast smoothly to a stop when not. In other words, require a greater velocity to - * maintain the fling once we enter overscroll. - */ - float threshold = (overscrolled && !mSubscroller.scrolling() ? STOPPED_THRESHOLD : FLING_STOPPED_THRESHOLD); - if (getVelocity() >= threshold) { - // we're still flinging - return; - } - - mX.stopFling(); - mY.stopFling(); - } - - /* Perform a bounce-back animation if overscrolled. */ - if (overscrolled) { - bounce(); - } else { - finishAnimation(); - setState(PanZoomState.NOTHING); - } - } - } - - private void finishAnimation() { - checkMainThread(); - - stopAnimationTask(); - - // Force a viewport synchronisation - mTarget.forceRedraw(null); - } - - /* Returns the nearest viewport metrics with no overscroll visible. */ - private ImmutableViewportMetrics getValidViewportMetrics() { - return getValidViewportMetrics(getMetrics()); - } - - private ImmutableViewportMetrics getValidViewportMetrics(ImmutableViewportMetrics viewportMetrics) { - /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */ - float zoomFactor = viewportMetrics.zoomFactor; - RectF pageRect = viewportMetrics.getPageRect(); - RectF viewport = viewportMetrics.getViewport(); - - float focusX = viewport.width() / 2.0f; - float focusY = viewport.height() / 2.0f; - - float minZoomFactor = 0.0f; - float maxZoomFactor = MAX_ZOOM; - - ZoomConstraints constraints = mTarget.getZoomConstraints(); - - if (constraints.getMinZoom() > 0) - minZoomFactor = constraints.getMinZoom(); - if (constraints.getMaxZoom() > 0) - maxZoomFactor = constraints.getMaxZoom(); - - if (!constraints.getAllowZoom()) { - // If allowZoom is false, clamp to the default zoom level. - maxZoomFactor = minZoomFactor = constraints.getDefaultZoom(); - } - - // Ensure minZoomFactor keeps the page at least as big as the viewport. - if (pageRect.width() > 0) { - float pageWidth = pageRect.width() + - viewportMetrics.marginLeft + - viewportMetrics.marginRight; - float scaleFactor = viewport.width() / pageWidth; - minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor); - if (viewport.width() > pageWidth) - focusX = 0.0f; - } - if (pageRect.height() > 0) { - float pageHeight = pageRect.height() + - viewportMetrics.marginTop + - viewportMetrics.marginBottom; - float scaleFactor = viewport.height() / pageHeight; - minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor); - if (viewport.height() > pageHeight) - focusY = 0.0f; - } - - maxZoomFactor = Math.max(maxZoomFactor, minZoomFactor); - - if (zoomFactor < minZoomFactor) { - // if one (or both) of the page dimensions is smaller than the viewport, - // zoom using the top/left as the focus on that axis. this prevents the - // scenario where, if both dimensions are smaller than the viewport, but - // by different scale factors, we end up scrolled to the end on one axis - // after applying the scale - PointF center = new PointF(focusX, focusY); - viewportMetrics = viewportMetrics.scaleTo(minZoomFactor, center); - } else if (zoomFactor > maxZoomFactor) { - PointF center = new PointF(viewport.width() / 2.0f, viewport.height() / 2.0f); - viewportMetrics = viewportMetrics.scaleTo(maxZoomFactor, center); - } - - /* Now we pan to the right origin. */ - viewportMetrics = viewportMetrics.clampWithMargins(); - - return viewportMetrics; - } - - private class AxisX extends Axis { - AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); } - @Override - public float getOrigin() { return getMetrics().viewportRectLeft; } - @Override - protected float getViewportLength() { return getMetrics().getWidth(); } - @Override - protected float getPageStart() { return getMetrics().pageRectLeft; } - @Override - protected float getMarginStart() { return mTarget.getMaxMargins().left - getMetrics().marginLeft; } - @Override - protected float getMarginEnd() { return mTarget.getMaxMargins().right - getMetrics().marginRight; } - @Override - protected float getPageLength() { return getMetrics().getPageWidthWithMargins(); } - @Override - protected boolean marginsHidden() { - ImmutableViewportMetrics metrics = getMetrics(); - RectF maxMargins = mTarget.getMaxMargins(); - return (metrics.marginLeft < maxMargins.left || metrics.marginRight < maxMargins.right); - } - @Override - protected void overscrollFling(final float velocity) { - if (mOverscroll != null) { - mOverscroll.setVelocity(velocity, Overscroll.Axis.X); - } - } - @Override - protected void overscrollPan(final float distance) { - if (mOverscroll != null) { - mOverscroll.setDistance(distance, Overscroll.Axis.X); - } - } - } - - private class AxisY extends Axis { - AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); } - @Override - public float getOrigin() { return getMetrics().viewportRectTop; } - @Override - protected float getViewportLength() { return getMetrics().getHeight(); } - @Override - protected float getPageStart() { return getMetrics().pageRectTop; } - @Override - protected float getPageLength() { return getMetrics().getPageHeightWithMargins(); } - @Override - protected float getMarginStart() { return mTarget.getMaxMargins().top - getMetrics().marginTop; } - @Override - protected float getMarginEnd() { return mTarget.getMaxMargins().bottom - getMetrics().marginBottom; } - @Override - protected boolean marginsHidden() { - ImmutableViewportMetrics metrics = getMetrics(); - RectF maxMargins = mTarget.getMaxMargins(); - return (metrics.marginTop < maxMargins.top || metrics.marginBottom < maxMargins.bottom); - } - @Override - protected void overscrollFling(final float velocity) { - if (mOverscroll != null) { - mOverscroll.setVelocity(velocity, Overscroll.Axis.Y); - } - } - @Override - protected void overscrollPan(final float distance) { - if (mOverscroll != null) { - mOverscroll.setDistance(distance, Overscroll.Axis.Y); - } - } - } - - /* - * Zooming - */ - @Override - public boolean onScaleBegin(SimpleScaleGestureDetector detector) { - if (mState == PanZoomState.ANIMATED_ZOOM) - return false; - - if (!mTarget.getZoomConstraints().getAllowZoom()) - return false; - - setState(PanZoomState.PINCHING); - mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY()); - cancelTouch(); - - //GeckoAppShell.sendEventToGecko(GeckoEvent.createNativeGestureEvent(GeckoEvent.ACTION_MAGNIFY_START, mLastZoomFocus, getMetrics().zoomFactor)); - - return true; - } - - @Override - public boolean onScale(SimpleScaleGestureDetector detector) { - if (mTarget.isFullScreen()) - return false; - - if (mState != PanZoomState.PINCHING) - return false; - - float prevSpan = detector.getPreviousSpan(); - if (FloatUtils.fuzzyEquals(prevSpan, 0.0f)) { - // let's eat this one to avoid setting the new zoom to infinity (bug 711453) - return true; - } - - synchronized (mTarget.getLock()) { - float zoomFactor = getAdjustedZoomFactor(detector.getCurrentSpan() / prevSpan); - scrollBy(mLastZoomFocus.x - detector.getFocusX(), - mLastZoomFocus.y - detector.getFocusY()); - mLastZoomFocus.set(detector.getFocusX(), detector.getFocusY()); - ImmutableViewportMetrics target = getMetrics().scaleTo(zoomFactor, mLastZoomFocus); - - // If overscroll is diabled, prevent zooming outside the normal document pans. - if (mX.getOverScrollMode() == View.OVER_SCROLL_NEVER || mY.getOverScrollMode() == View.OVER_SCROLL_NEVER) { - target = getValidViewportMetrics(target); - } - mTarget.setViewportMetrics(target); - } - - //GeckoEvent event = GeckoEvent.createNativeGestureEvent(GeckoEvent.ACTION_MAGNIFY, mLastZoomFocus, getMetrics().zoomFactor); - //GeckoAppShell.sendEventToGecko(event); - - return true; - } - - private ImmutableViewportMetrics applyZoomDelta(ImmutableViewportMetrics metrics, float zoomDelta) { - float oldZoom = metrics.zoomFactor; - float newZoom = oldZoom + zoomDelta; - float adjustedZoom = getAdjustedZoomFactor(newZoom / oldZoom); - // since we don't have a particular focus to zoom to, just use the center - PointF center = new PointF(metrics.getWidth() / 2.0f, metrics.getHeight() / 2.0f); - metrics = metrics.scaleTo(adjustedZoom, center); - return metrics; - } - - private boolean animatedScale(float zoomDelta) { - if (mState != PanZoomState.NOTHING && mState != PanZoomState.BOUNCE) { - return false; - } - synchronized (mTarget.getLock()) { - ImmutableViewportMetrics metrics = applyZoomDelta(getMetrics(), zoomDelta); - bounce(getValidViewportMetrics(metrics), PanZoomState.BOUNCE); - } - return true; - } - - private float getAdjustedZoomFactor(float zoomRatio) { - /* - * Apply edge resistance if we're zoomed out smaller than the page size by scaling the zoom - * factor toward 1.0. - */ - float resistance = Math.min(mX.getEdgeResistance(true), mY.getEdgeResistance(true)); - if (zoomRatio > 1.0f) - zoomRatio = 1.0f + (zoomRatio - 1.0f) * resistance; - else - zoomRatio = 1.0f - (1.0f - zoomRatio) * resistance; - - float newZoomFactor = getMetrics().zoomFactor * zoomRatio; - float minZoomFactor = 0.0f; - float maxZoomFactor = MAX_ZOOM; - - ZoomConstraints constraints = mTarget.getZoomConstraints(); - - if (constraints.getMinZoom() > 0) - minZoomFactor = constraints.getMinZoom(); - if (constraints.getMaxZoom() > 0) - maxZoomFactor = constraints.getMaxZoom(); - - if (newZoomFactor < minZoomFactor) { - // apply resistance when zooming past minZoomFactor, - // such that it asymptotically reaches minZoomFactor / 2.0 - // but never exceeds that - final float rate = 0.5f; // controls how quickly we approach the limit - float excessZoom = minZoomFactor - newZoomFactor; - excessZoom = 1.0f - (float)Math.exp(-excessZoom * rate); - newZoomFactor = minZoomFactor * (1.0f - excessZoom / 2.0f); - } - - if (newZoomFactor > maxZoomFactor) { - // apply resistance when zooming past maxZoomFactor, - // such that it asymptotically reaches maxZoomFactor + 1.0 - // but never exceeds that - float excessZoom = newZoomFactor - maxZoomFactor; - excessZoom = 1.0f - (float)Math.exp(-excessZoom); - newZoomFactor = maxZoomFactor + excessZoom; - } - - return newZoomFactor; - } - - @Override - public void onScaleEnd(SimpleScaleGestureDetector detector) { - if (mState == PanZoomState.ANIMATED_ZOOM) - return; - - // switch back to the touching state - startTouch(detector.getFocusX(), detector.getFocusY(), detector.getEventTime()); - - // Force a viewport synchronisation - mTarget.forceRedraw(null); - - PointF point = new PointF(detector.getFocusX(), detector.getFocusY()); - //GeckoEvent event = GeckoEvent.createNativeGestureEvent(GeckoEvent.ACTION_MAGNIFY_END, point, getMetrics().zoomFactor); - - //if (event == null) { - // return; - //} - - //GeckoAppShell.sendEventToGecko(event); - } - - @Override - public boolean getRedrawHint() { - switch (mState) { - case PINCHING: - case ANIMATED_ZOOM: - case BOUNCE: - // don't redraw during these because the zoom is (or might be, in the case - // of BOUNCE) be changing rapidly and gecko will have to redraw the entire - // display port area. we trigger a force-redraw upon exiting these states. - return false; - default: - // allow redrawing in other states - return true; - } - } - - private void sendPointToGecko(String event, MotionEvent motionEvent) { - String json; - try { - PointF point = new PointF(motionEvent.getX(), motionEvent.getY()); - point = mTarget.convertViewPointToLayerPoint(point); - if (point == null) { - return; - } - json = PointUtils.toJSON(point).toString(); - } catch (Exception e) { - Log.e(LOGTAG, "Unable to convert point to JSON for " + event, e); - return; - } - - //GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(event, json)); - } - - @Override - public boolean onDown(MotionEvent motionEvent) { - mMediumPress = false; - return false; - } - - @Override - public void onShowPress(MotionEvent motionEvent) { - // If we get this, it will be followed either by a call to - // onSingleTapUp (if the user lifts their finger before the - // long-press timeout) or a call to onLongPress (if the user - // does not). In the former case, we want to make sure it is - // treated as a click. (Note that if this is called, we will - // not get a call to onDoubleTap). - mMediumPress = true; - } - - @Override - public void onLongPress(MotionEvent motionEvent) { - sendPointToGecko("Gesture:LongPress", motionEvent); - } - - @Override - public boolean onSingleTapUp(MotionEvent motionEvent) { - // When zooming is enabled, we wait to see if there's a double-tap. - // However, if mMediumPress is true then we know there will be no - // double-tap so we treat this as a click. - if (mMediumPress || !mTarget.getZoomConstraints().getAllowZoom()) { - sendPointToGecko("Gesture:SingleTap", motionEvent); - } - // return false because we still want to get the ACTION_UP event that triggers this - return false; - } - - @Override - public boolean onSingleTapConfirmed(MotionEvent motionEvent) { - // When zooming is disabled, we handle this in onSingleTapUp. - if (mTarget.getZoomConstraints().getAllowZoom()) { - sendPointToGecko("Gesture:SingleTap", motionEvent); - } - return true; - } - - @Override - public boolean onDoubleTap(MotionEvent motionEvent) { - if (mTarget.getZoomConstraints().getAllowZoom()) { - sendPointToGecko("Gesture:DoubleTap", motionEvent); - } - return true; - } - - private void cancelTouch() { - //GeckoEvent e = GeckoEvent.createBroadcastEvent("Gesture:CancelTouch", ""); - //GeckoAppShell.sendEventToGecko(e); - } - - /** - * Zoom to a specified rect IN CSS PIXELS. - * - * While we usually use device pixels, @zoomToRect must be specified in CSS - * pixels. - */ - private ImmutableViewportMetrics getMetricsToZoomTo(RectF zoomToRect) { - final float startZoom = getMetrics().zoomFactor; - - RectF viewport = getMetrics().getViewport(); - // 1. adjust the aspect ratio of zoomToRect to match that of the current viewport, - // enlarging as necessary (if it gets too big, it will get shrunk in the next step). - // while enlarging make sure we enlarge equally on both sides to keep the target rect - // centered. - float targetRatio = viewport.width() / viewport.height(); - float rectRatio = zoomToRect.width() / zoomToRect.height(); - if (FloatUtils.fuzzyEquals(targetRatio, rectRatio)) { - // all good, do nothing - } else if (targetRatio < rectRatio) { - // need to increase zoomToRect height - float newHeight = zoomToRect.width() / targetRatio; - zoomToRect.top -= (newHeight - zoomToRect.height()) / 2; - zoomToRect.bottom = zoomToRect.top + newHeight; - } else { // targetRatio > rectRatio) { - // need to increase zoomToRect width - float newWidth = targetRatio * zoomToRect.height(); - zoomToRect.left -= (newWidth - zoomToRect.width()) / 2; - zoomToRect.right = zoomToRect.left + newWidth; - } - - float finalZoom = viewport.width() / zoomToRect.width(); - - ImmutableViewportMetrics finalMetrics = getMetrics(); - finalMetrics = finalMetrics.setViewportOrigin( - zoomToRect.left * finalMetrics.zoomFactor, - zoomToRect.top * finalMetrics.zoomFactor); - finalMetrics = finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f)); - - // 2. now run getValidViewportMetrics on it, so that the target viewport is - // clamped down to prevent overscroll, over-zoom, and other bad conditions. - finalMetrics = getValidViewportMetrics(finalMetrics); - return finalMetrics; - } - - private boolean animatedZoomTo(RectF zoomToRect) { - bounce(getMetricsToZoomTo(zoomToRect), PanZoomState.ANIMATED_ZOOM); - return true; - } - - /** This function must be called from the UI thread. */ - @Override - public void abortPanning() { - checkMainThread(); - bounce(); - } - - @Override - public void setOverScrollMode(int overscrollMode) { - mX.setOverScrollMode(overscrollMode); - mY.setOverScrollMode(overscrollMode); - } - - @Override - public int getOverScrollMode() { - return mX.getOverScrollMode(); - } - - @Override - public void setOverscrollHandler(final Overscroll handler) { - mOverscroll = handler; - } -} |