diff options
author | Tomaž Vajngerl <tomaz.vajngerl@collabora.com> | 2014-09-26 23:15:29 +0200 |
---|---|---|
committer | Tomaž Vajngerl <tomaz.vajngerl@collabora.com> | 2014-09-26 23:17:22 +0200 |
commit | 93e7ffbd1bd38ae9fad63a0a8abd2deb7fab9543 (patch) | |
tree | bcaa5d6820fbe4dc6278c3c61c3649d69c15263c /android | |
parent | dc169a05cfa11bbf63294b4a930f51c330d363ed (diff) |
android: PanZoomControler updates from Fennec
Change-Id: Ic338221ced6133771af8f46ae4a257e043a1709f
Diffstat (limited to 'android')
6 files changed, 119 insertions, 128 deletions
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java index 59fc1fea2573..8a39b82c259f 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java @@ -80,6 +80,10 @@ public class LayerController { layerClient.setLayerController(this); } + public void destroy() { + mPanZoomController.destroy(); + } + public void setForceRedraw() { mForceRedraw = true; } @@ -93,26 +97,10 @@ public class LayerController { return mViewportMetrics.getViewport(); } - public RectF getCssViewport() { - return mViewportMetrics.getCssViewport(); - } - public FloatSize getViewportSize() { return mViewportMetrics.getSize(); } - public RectF getPageRect() { - return mViewportMetrics.getPageRect(); - } - - public RectF getCssPageRect() { - return mViewportMetrics.getCssPageRect(); - } - - public PointF getOrigin() { - return mViewportMetrics.getOrigin(); - } - public float getZoomFactor() { return mViewportMetrics.zoomFactor; } @@ -321,11 +309,6 @@ public class LayerController { public void setAllowZoom(final boolean aValue) { mAllowZoom = aValue; - mView.post(new Runnable() { - public void run() { - mView.getTouchEventHandler().setDoubleTapEnabled(aValue); - } - }); } public boolean getAllowZoom() { diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java index f7d8c4efe8d3..c6f11f4ba089 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java @@ -188,11 +188,9 @@ public class LayerRenderer implements GLSurfaceView.Renderer { @Override protected void finalize() throws Throwable { try { - if (mCoordByteBuffer != null) { - DirectBufferAllocator.free(mCoordByteBuffer); - mCoordByteBuffer = null; - mCoordBuffer = null; - } + DirectBufferAllocator.free(mCoordByteBuffer); + mCoordByteBuffer = null; + mCoordBuffer = null; } finally { super.finalize(); } @@ -333,14 +331,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer { moveFrameRateLayer(width, height); } - // updating the state in the view/controller/client should be - // done on the main UI thread, not the GL renderer thread - mView.post(new Runnable() { - public void run() { - mView.setViewportSize(new IntSize(width, height)); - } - }); - /* TODO: Throw away tile images? */ } diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java index 9c6a61698926..18f6328d55d7 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java @@ -74,6 +74,13 @@ public class LayerView extends SurfaceView implements SurfaceHolder.Callback { @Override public boolean onTouchEvent(MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) + requestFocus(); + return mTouchEventHandler.handleEvent(event); + } + + @Override + public boolean onHoverEvent(MotionEvent event) { return mTouchEventHandler.handleEvent(event); } diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java index 78a141ee0343..b93b5f0a1ff6 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java @@ -7,6 +7,7 @@ package org.mozilla.gecko.gfx; import android.content.Context; import android.os.SystemClock; +import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View.OnTouchListener; @@ -58,7 +59,6 @@ public final class TouchEventHandler { private final GestureDetector mGestureDetector; private final SimpleScaleGestureDetector mScaleGestureDetector; private final PanZoomController mPanZoomController; - private final GestureDetector.OnDoubleTapListener mDoubleTapListener; // the queue of events that we are holding on to while waiting for a preventDefault // notification @@ -133,8 +133,7 @@ public final class TouchEventHandler { mListenerTimeoutProcessor = new ListenerTimeoutProcessor(); mDispatchEvents = true; - mDoubleTapListener = controller.getDoubleTapListener(); - setDoubleTapEnabled(true); + mGestureDetector.setOnDoubleTapListener(controller.getDoubleTapListener()); } /* This function MUST be called on the UI thread */ @@ -146,6 +145,12 @@ public final class TouchEventHandler { return true; } + // if this is a hover event just notify gecko, we don't have any interest in the java layer. + if (isHoverEvent(event)) { + mOnTouchListener.onTouch(mView, event); + return true; + } + if (isDownEvent(event)) { // this is the start of a new block of events! whee! mHoldInQueue = mWaitForTouchListeners; @@ -159,18 +164,20 @@ public final class TouchEventHandler { // other blocks waiting in the queue, then we should let the pan/zoom controller // know we are waiting for the touch listeners to run if (mEventQueue.isEmpty()) { - mPanZoomController.waitingForTouchListeners(event); + mPanZoomController.startingNewEventBlock(event, true); } - // if we're holding the events in the queue, set the timeout so that - // we dispatch these events if we don't get a default-prevented notification - mView.postDelayed(mListenerTimeoutProcessor, EVENT_LISTENER_TIMEOUT); } else { - // if we're not holding these events, then we still need to pretend like - // we did and had a ListenerTimeoutProcessor fire so that when we get - // the default-prevented notification for this block, it doesn't accidentally - // act upon some other block - mProcessingBalance++; + // we're not going to be holding this block of events in the queue, but we need + // a marker of some sort so that the processEventBlock loop deals with the blocks + // in the right order as notifications come in. we use a single null event in + // the queue as a placeholder for a block of events that has already been dispatched. + mEventQueue.add(null); + mPanZoomController.startingNewEventBlock(event, false); } + + // set the timeout so that we dispatch these events and update mProcessingBalance + // if we don't get a default-prevented notification + mView.postDelayed(mListenerTimeoutProcessor, EVENT_LISTENER_TIMEOUT); } // if we need to hold the events, add it to the queue. if we need to dispatch @@ -213,11 +220,6 @@ public final class TouchEventHandler { } /* This function MUST be called on the UI thread. */ - public void setDoubleTapEnabled(boolean aValue) { - mGestureDetector.setOnDoubleTapListener(aValue ? mDoubleTapListener : null); - } - - /* This function MUST be called on the UI thread. */ public void setWaitForTouchListeners(boolean aValue) { mWaitForTouchListeners = aValue; } @@ -227,6 +229,11 @@ public final class TouchEventHandler { mOnTouchListener = onTouchListener; } + private boolean isHoverEvent(MotionEvent event) { + int action = (event.getAction() & MotionEvent.ACTION_MASK); + return (action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_MOVE || action == MotionEvent.ACTION_HOVER_EXIT); + } + private boolean isDownEvent(MotionEvent event) { int action = (event.getAction() & MotionEvent.ACTION_MASK); return (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN); @@ -242,16 +249,7 @@ public final class TouchEventHandler { */ private void dispatchEvent(MotionEvent event) { if (mGestureDetector.onTouchEvent(event)) { - // An up/cancel event should get passed to both detectors, in - // case it comes from a pointer the scale detector is tracking. - switch (event.getAction() & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - break; - default: - return; - } + return; } mScaleGestureDetector.onTouchEvent(event); if (mScaleGestureDetector.isInProgress()) { @@ -272,6 +270,11 @@ public final class TouchEventHandler { dispatchEvent(MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0)); } + if (mEventQueue.isEmpty()) { + Log.e(LOGTAG, "Unexpected empty event queue in processEventBlock!", new Exception()); + return; + } + // the odd loop condition is because the first event in the queue will // always be a DOWN or POINTER_DOWN event, and we want to process all // the events in the queue starting at that one, up to but not including @@ -279,15 +282,19 @@ public final class TouchEventHandler { MotionEvent event = mEventQueue.poll(); while (true) { - // for each event we process, only dispatch it if the block hasn't been - // default-prevented. - if (allowDefaultAction) { - dispatchEvent(event); - } else if (touchFinished(event)) { - mPanZoomController.preventedTouchFinished(); + // event being null here is valid and represents a block of events + // that has already been dispatched. + + if (event != null) { + // for each event we process, only dispatch it if the block hasn't been + // default-prevented. + if (allowDefaultAction) { + dispatchEvent(event); + } else if (touchFinished(event)) { + mPanZoomController.preventedTouchFinished(); + } } - event = mEventQueue.peek(); - if (event == null) { + if (mEventQueue.isEmpty()) { // we have processed the backlog of events, and are all caught up. // now we can set clear the hold flag and set the dispatch flag so // that the handleEvent() function can do the right thing for all @@ -297,10 +304,13 @@ public final class TouchEventHandler { mDispatchEvents = allowDefaultAction; break; } - if (isDownEvent(event)) { + event = mEventQueue.peek(); + if (event == null || isDownEvent(event)) { // we have finished processing the block we were interested in. // now we wait for the next call to processEventBlock - mPanZoomController.waitingForTouchListeners(event); + if (event != null) { + mPanZoomController.startingNewEventBlock(event, true); + } break; } // pop the event we peeked above, as it is still part of the block and diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java index 0cb83ef79d8c..243fa75d2129 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java @@ -14,6 +14,7 @@ import android.view.MotionEvent; import org.libreoffice.LOKitShell; import org.libreoffice.LibreOfficeMainActivity; +import org.mozilla.gecko.gfx.ImmutableViewportMetrics; import org.mozilla.gecko.gfx.LayerController; import org.mozilla.gecko.gfx.ViewportMetrics; import org.mozilla.gecko.util.FloatUtils; @@ -117,7 +118,19 @@ public class PanZoomController mMainThread = LibreOfficeMainActivity.mAppContext.getMainLooper().getThread(); checkMainThread(); - mState = PanZoomState.NOTHING; + setState(PanZoomState.NOTHING); + } + + public void destroy() { + mSubscroller.destroy(); + } + + private void setState(PanZoomState state) { + mState = state; + } + + private ImmutableViewportMetrics getMetrics() { + return mController.getViewportMetrics(); } // for debugging bug 713011; it can be taken out once that is resolved. @@ -171,7 +184,7 @@ public class PanZoomController 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 - mState = PanZoomState.NOTHING; + setState(PanZoomState.NOTHING); // fall through case NOTHING: // Don't do animations here; they're distracting and can cause flashes on page @@ -185,14 +198,14 @@ public class PanZoomController } /** This function must be called on the UI thread. */ - public void waitingForTouchListeners(MotionEvent event) { + public void startingNewEventBlock(MotionEvent event, boolean waitingForTouchListeners) { checkMainThread(); - if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + 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 - mSubscroller.cancel(); // seting the state will kill any animations in progress, possibly leaving // the page in overscroll - mState = PanZoomState.WAITING_LISTENERS; + setState(PanZoomState.WAITING_LISTENERS); } } @@ -203,7 +216,6 @@ public class PanZoomController // if we enter here, we just finished a block of events whose default actions // were prevented by touch listeners. Now there are no touch points left, so // we need to reset our state and re-bounce because we might be in overscroll - mState = PanZoomState.NOTHING; bounce(); } } @@ -213,7 +225,7 @@ public class PanZoomController if (mState == PanZoomState.NOTHING) { synchronized (mController) { ViewportMetrics validated = getValidViewportMetrics(); - if (! (new ViewportMetrics(mController.getViewportMetrics())).fuzzyEquals(validated)) { + if (! (new ViewportMetrics(getMetrics())).fuzzyEquals(validated)) { // page size changed such that we are now in overscroll. snap to the // the nearest valid viewport mController.setViewportMetrics(validated); @@ -231,7 +243,6 @@ public class PanZoomController // user is taking control of movement, so stop // any auto-movement we have going stopAnimationTimer(); - mSubscroller.cancel(); switch (mState) { case ANIMATED_ZOOM: @@ -285,14 +296,14 @@ public class PanZoomController return true; case PANNING_HOLD_LOCKED: - mState = PanZoomState.PANNING_LOCKED; + setState(PanZoomState.PANNING_LOCKED); // fall through case PANNING_LOCKED: track(event); return true; case PANNING_HOLD: - mState = PanZoomState.PANNING; + setState(PanZoomState.PANNING); // fall through case PANNING: track(event); @@ -322,7 +333,6 @@ public class PanZoomController return false; case TOUCHING: - mState = PanZoomState.NOTHING; // 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 @@ -333,12 +343,12 @@ public class PanZoomController case PANNING_LOCKED: case PANNING_HOLD: case PANNING_HOLD_LOCKED: - mState = PanZoomState.FLING; + setState(PanZoomState.FLING); fling(); return true; case PINCHING: - mState = PanZoomState.NOTHING; + setState(PanZoomState.NOTHING); return true; } Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchEnd"); @@ -357,7 +367,6 @@ public class PanZoomController } cancelTouch(); - mState = PanZoomState.NOTHING; // ensure we snap back if we're overscrolled bounce(); return false; @@ -366,7 +375,7 @@ public class PanZoomController private void startTouch(float x, float y, long time) { mX.startTouch(x); mY.startTouch(y); - mState = PanZoomState.TOUCHING; + setState(PanZoomState.TOUCHING); mLastEventTime = time; } @@ -384,12 +393,12 @@ public class PanZoomController if (angle < AXIS_LOCK_ANGLE || angle > (Math.PI - AXIS_LOCK_ANGLE)) { mY.setScrollingDisabled(true); - mState = PanZoomState.PANNING_LOCKED; + setState(PanZoomState.PANNING_LOCKED); } else if (Math.abs(angle - (Math.PI / 2)) < AXIS_LOCK_ANGLE) { mX.setScrollingDisabled(true); - mState = PanZoomState.PANNING_LOCKED; + setState(PanZoomState.PANNING_LOCKED); } else { - mState = PanZoomState.PANNING; + setState(PanZoomState.PANNING); } } @@ -425,13 +434,13 @@ public class PanZoomController if (stopped()) { if (mState == PanZoomState.PANNING) { - mState = PanZoomState.PANNING_HOLD; + setState(PanZoomState.PANNING_HOLD); } else if (mState == PanZoomState.PANNING_LOCKED) { - mState = PanZoomState.PANNING_HOLD_LOCKED; + setState(PanZoomState.PANNING_HOLD_LOCKED); } else { // should never happen, but handle anyway for robustness Log.e(LOGTAG, "Impossible case " + mState + " when stopped in track"); - mState = PanZoomState.PANNING_HOLD_LOCKED; + setState(PanZoomState.PANNING_HOLD_LOCKED); } } @@ -456,9 +465,9 @@ public class PanZoomController private void bounce(ViewportMetrics metrics) { stopAnimationTimer(); - ViewportMetrics bounceStartMetrics = new ViewportMetrics(mController.getViewportMetrics()); + ViewportMetrics bounceStartMetrics = new ViewportMetrics(getMetrics()); if (bounceStartMetrics.fuzzyEquals(metrics)) { - mState = PanZoomState.NOTHING; + setState(PanZoomState.NOTHING); return; } @@ -472,7 +481,7 @@ public class PanZoomController /* Performs a bounce-back animation to the nearest valid viewport metrics. */ private void bounce() { - mState = PanZoomState.BOUNCE; + setState(PanZoomState.BOUNCE); bounce(getValidViewportMetrics()); } @@ -596,7 +605,7 @@ public class PanZoomController /* Finally, if there's nothing else to do, complete the animation and go to sleep. */ finishBounce(); finishAnimation(); - mState = PanZoomState.NOTHING; + setState(PanZoomState.NOTHING); } /* Performs one frame of a bounce animation. */ @@ -664,7 +673,7 @@ public class PanZoomController bounce(); } else { finishAnimation(); - mState = PanZoomState.NOTHING; + setState(PanZoomState.NOTHING); } } } @@ -672,7 +681,6 @@ public class PanZoomController private void finishAnimation() { checkMainThread(); - Log.d(LOGTAG, "Finishing animation at " + mController.getViewportMetrics()); stopAnimationTimer(); // Force a viewport synchronisation @@ -682,12 +690,10 @@ public class PanZoomController /* Returns the nearest viewport metrics with no overscroll visible. */ private ViewportMetrics getValidViewportMetrics() { - return getValidViewportMetrics(new ViewportMetrics(mController.getViewportMetrics())); + return getValidViewportMetrics(new ViewportMetrics(getMetrics())); } private ViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) { - Log.d(LOGTAG, "generating valid viewport using " + viewportMetrics); - /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */ float zoomFactor = viewportMetrics.getZoomFactor(); RectF pageRect = viewportMetrics.getPageRect(); @@ -740,7 +746,6 @@ public class PanZoomController /* Now we pan to the right origin. */ viewportMetrics.setViewport(viewportMetrics.getClampedViewport()); - Log.d(LOGTAG, "generated valid viewport as " + viewportMetrics); return viewportMetrics; } @@ -748,25 +753,25 @@ public class PanZoomController private class AxisX extends Axis { AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); } @Override - public float getOrigin() { return mController.getOrigin().x; } + public float getOrigin() { return getMetrics().viewportRectLeft; } @Override - protected float getViewportLength() { return mController.getViewportSize().width; } + protected float getViewportLength() { return getMetrics().getWidth(); } @Override - protected float getPageStart() { return mController.getPageRect().left; } + protected float getPageStart() { return getMetrics().pageRectLeft; } @Override - protected float getPageLength() { return mController.getPageRect().width(); } + protected float getPageLength() { return getMetrics().getPageWidth(); } } private class AxisY extends Axis { AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); } @Override - public float getOrigin() { return mController.getOrigin().y; } + public float getOrigin() { return getMetrics().viewportRectTop; } @Override - protected float getViewportLength() { return mController.getViewportSize().height; } + protected float getViewportLength() { return getMetrics().getHeight(); } @Override - protected float getPageStart() { return mController.getPageRect().top; } + protected float getPageStart() { return getMetrics().pageRectTop; } @Override - protected float getPageLength() { return mController.getPageRect().height(); } + protected float getPageLength() { return getMetrics().getPageHeight(); } } /* @@ -774,15 +779,13 @@ public class PanZoomController */ @Override public boolean onScaleBegin(SimpleScaleGestureDetector detector) { - Log.d(LOGTAG, "onScaleBegin in " + mState); - if (mState == PanZoomState.ANIMATED_ZOOM) return false; if (!mController.getAllowZoom()) return false; - mState = PanZoomState.PINCHING; + setState(PanZoomState.PINCHING); mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY()); cancelTouch(); @@ -791,9 +794,6 @@ public class PanZoomController @Override public boolean onScale(SimpleScaleGestureDetector detector) { - Log.d(LOGTAG, "onScale in state " + mState); - - if (mState != PanZoomState.PINCHING) return false; @@ -857,8 +857,6 @@ public class PanZoomController @Override public void onScaleEnd(SimpleScaleGestureDetector detector) { - Log.d(LOGTAG, "onScaleEnd in " + mState); - if (mState == PanZoomState.ANIMATED_ZOOM) return; @@ -892,23 +890,17 @@ public class PanZoomController @Override public boolean onSingleTapUp(MotionEvent motionEvent) { // When zooming is enabled, wait to see if there's a double-tap. - if (mController.getAllowZoom()) - return false; - return true; + return false; } @Override public boolean onSingleTapConfirmed(MotionEvent motionEvent) { // When zooming is disabled, we handle this in onSingleTapUp. - if (!mController.getAllowZoom()) - return false; return true; } @Override public boolean onDoubleTap(MotionEvent motionEvent) { - if (!mController.getAllowZoom()) - return false; return true; } @@ -922,7 +914,7 @@ public class PanZoomController * pixels. */ private boolean animatedZoomTo(RectF zoomToRect) { - mState = PanZoomState.ANIMATED_ZOOM; + setState(PanZoomState.ANIMATED_ZOOM); final float startZoom = mController.getZoomFactor(); RectF viewport = mController.getViewport(); @@ -948,7 +940,7 @@ public class PanZoomController float finalZoom = viewport.width() / zoomToRect.width(); - ViewportMetrics finalMetrics = new ViewportMetrics(mController.getViewportMetrics()); + ViewportMetrics finalMetrics = new ViewportMetrics(getMetrics()); finalMetrics.setOrigin(new PointF(zoomToRect.left * finalMetrics.getZoomFactor(), zoomToRect.top * finalMetrics.getZoomFactor())); finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f)); @@ -960,4 +952,10 @@ public class PanZoomController bounce(finalMetrics); return true; } + + /** This function must be called from the UI thread. */ + public void abortPanning() { + checkMainThread(); + bounce(); + } } diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java index 8e7de558e18d..0e74456896ec 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java @@ -45,6 +45,9 @@ class SubdocumentScrollHelper { mPendingDisplacement = new PointF(); } + void destroy() { + } + boolean scrollBy(PointF displacement) { if (! mOverridePanning) { return false; |