summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomaž Vajngerl <tomaz.vajngerl@collabora.com>2014-09-27 22:54:20 +0200
committerTomaž Vajngerl <tomaz.vajngerl@collabora.com>2014-09-28 22:32:48 +0200
commitf66ff689d0b3ba5196cac717c7228f541d853e3f (patch)
treee75a1b457314d17cb3d97923c5987e816822e3cc
parent590fdf41164347f113cccc01953eefe6ef1020a8 (diff)
android: Improve panning smoothness (Fennec import)
Change-Id: I3983709651548eb97e588ebe2c2de608a4a4dfc7
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java92
-rw-r--r--android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java70
2 files changed, 88 insertions, 74 deletions
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java
index fcdcd727ee42..7ae80843a892 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java
@@ -6,8 +6,8 @@
package org.mozilla.gecko.ui;
import android.util.Log;
+import android.view.View;
-import org.json.JSONArray;
import org.mozilla.gecko.util.FloatUtils;
import java.util.Map;
@@ -23,7 +23,6 @@ abstract class Axis {
private static final String PREF_SCROLLING_FRICTION_SLOW = "ui.scrolling.friction_slow";
private static final String PREF_SCROLLING_FRICTION_FAST = "ui.scrolling.friction_fast";
- private static final String PREF_SCROLLING_VELOCITY_THRESHOLD = "ui.scrolling.velocity_threshold";
private static final String PREF_SCROLLING_MAX_EVENT_ACCELERATION = "ui.scrolling.max_event_acceleration";
private static final String PREF_SCROLLING_OVERSCROLL_DECEL_RATE = "ui.scrolling.overscroll_decel_rate";
private static final String PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT = "ui.scrolling.overscroll_snap_limit";
@@ -59,22 +58,22 @@ abstract class Axis {
return (value == null || value < 0 ? defaultValue : value);
}
- static void addPrefNames(JSONArray prefs) {
- prefs.put(PREF_SCROLLING_FRICTION_FAST);
- prefs.put(PREF_SCROLLING_FRICTION_SLOW);
- prefs.put(PREF_SCROLLING_VELOCITY_THRESHOLD);
- prefs.put(PREF_SCROLLING_MAX_EVENT_ACCELERATION);
- prefs.put(PREF_SCROLLING_OVERSCROLL_DECEL_RATE);
- prefs.put(PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT);
- prefs.put(PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE);
+ static final float MS_PER_FRAME = 4.0f;
+ private static final float FRAMERATE_MULTIPLIER = (1000f/60f) / MS_PER_FRAME;
+
+ // The values we use for friction are based on a 16.6ms frame, adjust them to MS_PER_FRAME:
+ // FRICTION^1 = FRICTION_ADJUSTED^(16/MS_PER_FRAME)
+ // FRICTION_ADJUSTED = e ^ ((ln(FRICTION))/FRAMERATE_MULTIPLIER)
+ static float getFrameAdjustedFriction(float baseFriction) {
+ return (float)Math.pow(Math.E, (Math.log(baseFriction) / FRAMERATE_MULTIPLIER));
}
static void setPrefs(Map<String, Integer> prefs) {
- FRICTION_SLOW = getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850);
- FRICTION_FAST = getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970);
- VELOCITY_THRESHOLD = getIntPref(prefs, PREF_SCROLLING_VELOCITY_THRESHOLD, 10);
+ FRICTION_SLOW = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850));
+ FRICTION_FAST = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970));
+ VELOCITY_THRESHOLD = 10 / FRAMERATE_MULTIPLIER;
MAX_EVENT_ACCELERATION = getFloatPref(prefs, PREF_SCROLLING_MAX_EVENT_ACCELERATION, 12);
- OVERSCROLL_DECEL_RATE = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40);
+ OVERSCROLL_DECEL_RATE = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40));
SNAP_LIMIT = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT, 300);
MIN_SCROLLABLE_DISTANCE = getFloatPref(prefs, PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE, 500);
Log.i(LOGTAG, "Prefs: " + FRICTION_SLOW + "," + FRICTION_FAST + "," + VELOCITY_THRESHOLD + ","
@@ -86,9 +85,6 @@ abstract class Axis {
setPrefs(null);
}
- // The number of milliseconds per frame assuming 60 fps
- private static final float MS_PER_FRAME = 1000.0f / 60.0f;
-
private enum FlingStates {
STOPPED,
PANNING,
@@ -104,6 +100,7 @@ abstract class Axis {
private final SubdocumentScrollHelper mSubscroller;
+ private int mOverscrollMode; /* Default to only overscrolling if we're allowed to scroll in a direction */
private float mFirstTouchPos; /* Position of the first touch event on the current drag. */
private float mTouchPos; /* Position of the most recent touch event on the current drag. */
private float mLastTouchPos; /* Position of the touch event before touchPos. */
@@ -121,6 +118,15 @@ abstract class Axis {
Axis(SubdocumentScrollHelper subscroller) {
mSubscroller = subscroller;
+ mOverscrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
+ }
+
+ public void setOverScrollMode(int overscrollMode) {
+ mOverscrollMode = overscrollMode;
+ }
+
+ public int getOverScrollMode() {
+ return mOverscrollMode;
}
private float getViewportEnd() {
@@ -155,7 +161,7 @@ abstract class Axis {
// If there's a direction change, or current velocity is very low,
// allow setting of the velocity outright. Otherwise, use the current
// velocity and a maximum change factor to set the new velocity.
- boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f;
+ boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f / FRAMERATE_MULTIPLIER;
boolean directionChange = (mVelocity > 0) != (newVelocity > 0);
if (curVelocityIsLow || (directionChange && !FloatUtils.fuzzyEquals(newVelocity, 0.0f))) {
mVelocity = newVelocity;
@@ -200,24 +206,31 @@ abstract class Axis {
* Returns true if the page is zoomed in to some degree along this axis such that scrolling is
* possible and this axis has not been scroll locked while panning. Otherwise, returns false.
*/
- private boolean scrollable() {
+ boolean scrollable() {
// If we're scrolling a subdocument, ignore the viewport length restrictions (since those
// apply to the top-level document) and only take into account axis locking.
if (mSubscroller.scrolling()) {
return !mScrollingDisabled;
- } else {
- return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE &&
- !mScrollingDisabled;
}
+
+ // if we are axis locked, return false
+ if (mScrollingDisabled) {
+ return false;
+ }
+
+ // there is scrollable space, and we're not disabled, or the document fits the viewport
+ // but we always allow overscroll anyway
+ return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE ||
+ getOverScrollMode() == View.OVER_SCROLL_ALWAYS;
}
/*
* Returns the resistance, as a multiplier, that should be taken into account when
* tracking or pinching.
*/
- float getEdgeResistance() {
+ float getEdgeResistance(boolean forPinching) {
float excess = getExcess();
- if (excess > 0.0f) {
+ if (excess > 0.0f && (getOverscroll() == Overscroll.BOTH || !forPinching)) {
// excess can be greater than viewport length, but the resistance
// must never drop below 0.0
return Math.max(0.0f, SNAP_LIMIT - excess / getViewportLength());
@@ -257,7 +270,15 @@ abstract class Axis {
}
float excess = getExcess();
- if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f)) {
+ Overscroll overscroll = getOverscroll();
+ boolean decreasingOverscroll = false;
+ if ((overscroll == Overscroll.MINUS && mVelocity > 0) ||
+ (overscroll == Overscroll.PLUS && mVelocity < 0))
+ {
+ decreasingOverscroll = true;
+ }
+
+ if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f) || decreasingOverscroll) {
// If we aren't overscrolled, just apply friction.
if (Math.abs(mVelocity) >= VELOCITY_THRESHOLD) {
mVelocity *= FRICTION_FAST;
@@ -268,7 +289,7 @@ abstract class Axis {
} else {
// Otherwise, decrease the velocity linearly.
float elasticity = 1.0f - excess / (getViewportLength() * SNAP_LIMIT);
- if (getOverscroll() == Overscroll.MINUS) {
+ if (overscroll == Overscroll.MINUS) {
mVelocity = Math.min((mVelocity + OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
} else { // must be Overscroll.PLUS
mVelocity = Math.max((mVelocity - OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
@@ -285,14 +306,27 @@ abstract class Axis {
// Performs displacement of the viewport position according to the current velocity.
void displace() {
- if (!scrollable()) {
+ // if this isn't scrollable just return
+ if (!scrollable())
return;
- }
if (mFlingState == FlingStates.PANNING)
- mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance();
+ mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance(false);
else
mDisplacement += mVelocity;
+
+ // if overscroll is disabled and we're trying to overscroll, reset the displacement
+ // to remove any excess. Using getExcess alone isn't enough here since it relies on
+ // getOverscroll which doesn't take into account any new displacment being applied
+ if (getOverScrollMode() == View.OVER_SCROLL_NEVER) {
+ if (mDisplacement + getOrigin() < getPageStart()) {
+ mDisplacement = getPageStart() - getOrigin();
+ stopFling();
+ } else if (mDisplacement + getViewportEnd() > getPageEnd()) {
+ mDisplacement = getPageEnd() - getViewportEnd();
+ stopFling();
+ }
+ }
}
float resetDisplacement() {
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 a6b7d2785c75..02e45daf3a15 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
@@ -19,8 +19,6 @@ import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
import org.mozilla.gecko.gfx.ViewportMetrics;
import org.mozilla.gecko.util.FloatUtils;
-import java.util.Arrays;
-import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;
@@ -53,26 +51,6 @@ public class PanZoomController
// The maximum amount we allow you to zoom into a page
private static final float MAX_ZOOM = 4.0f;
- /* 16 precomputed frames of the _ease-out_ animation from the CSS Transitions specification. */
- private static float[] ZOOM_ANIMATION_FRAMES = new float[] {
- 0.00000f, /* 0 */
- 0.10211f, /* 1 */
- 0.19864f, /* 2 */
- 0.29043f, /* 3 */
- 0.37816f, /* 4 */
- 0.46155f, /* 5 */
- 0.54054f, /* 6 */
- 0.61496f, /* 7 */
- 0.68467f, /* 8 */
- 0.74910f, /* 9 */
- 0.80794f, /* 10 */
- 0.86069f, /* 11 */
- 0.90651f, /* 12 */
- 0.94471f, /* 13 */
- 0.97401f, /* 14 */
- 0.99309f, /* 15 */
- };
-
private enum PanZoomState {
NOTHING, /* no touch-start events received */
FLING, /* all touches removed, but we're still scrolling page */
@@ -125,6 +103,13 @@ public class PanZoomController
mSubscroller.destroy();
}
+ private final static float easeOut(float t) {
+ // ease-out approx.
+ // -(t-1)^2+1
+ t = t-1;
+ return -t*t+1;
+ }
+
private void setState(PanZoomState state) {
mState = state;
}
@@ -145,23 +130,6 @@ public class PanZoomController
}
}
- private void setZoomAnimationFrames(String frames) {
- try {
- if (frames.length() > 0) {
- StringTokenizer st = new StringTokenizer(frames, ",");
- float[] values = new float[st.countTokens()];
- for (int i = 0; i < values.length; i++) {
- values[i] = Float.parseFloat(st.nextToken());
- }
- ZOOM_ANIMATION_FRAMES = values;
- }
- } catch (NumberFormatException e) {
- Log.e(LOGTAG, "Error setting zoom animation frames", e);
- } finally {
- Log.i(LOGTAG, "Zoom animation frames: " + Arrays.toString(ZOOM_ANIMATION_FRAMES));
- }
- }
-
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: return onTouchStart(event);
@@ -357,6 +325,8 @@ public class PanZoomController
}
private boolean onTouchCancel(MotionEvent event) {
+ cancelTouch();
+
if (mState == PanZoomState.WAITING_LISTENERS) {
// we might get a cancel event from the TouchEventHandler while in the
// WAITING_LISTENERS state if the touch listeners prevent-default the
@@ -367,7 +337,6 @@ public class PanZoomController
return false;
}
- cancelTouch();
// ensure we snap back if we're overscrolled
bounce();
return false;
@@ -392,7 +361,9 @@ public class PanZoomController
mY.startTouch(y);
mLastEventTime = time;
- if (angle < AXIS_LOCK_ANGLE || angle > (Math.PI - AXIS_LOCK_ANGLE)) {
+ 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);
} else if (Math.abs(angle - (Math.PI / 2)) < AXIS_LOCK_ANGLE) {
@@ -507,7 +478,7 @@ public class PanZoomController
mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() { mTarget.post(runnable); }
- }, 0, 1000L/60L);
+ }, 0, (int)Axis.MS_PER_FRAME);
}
/* Stops the fling or bounce animation. */
@@ -607,7 +578,7 @@ public class PanZoomController
}
/* Perform the next frame of the bounce-back animation. */
- if (mBounceFrame < ZOOM_ANIMATION_FRAMES.length) {
+ if (mBounceFrame < (int)(256f/Axis.MS_PER_FRAME)) {
advanceBounce();
return;
}
@@ -621,7 +592,7 @@ public class PanZoomController
/* Performs one frame of a bounce animation. */
private void advanceBounce() {
synchronized (mTarget.getLock()) {
- float t = ZOOM_ANIMATION_FRAMES[mBounceFrame];
+ float t = easeOut(mBounceFrame * Axis.MS_PER_FRAME / 256f);
ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
mTarget.setViewportMetrics(newMetrics);
mBounceFrame++;
@@ -818,7 +789,7 @@ public class PanZoomController
* 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(), mY.getEdgeResistance());
+ float resistance = Math.min(mX.getEdgeResistance(true), mY.getEdgeResistance(true));
if (spanRatio > 1.0f)
spanRatio = 1.0f + (spanRatio - 1.0f) * resistance;
else
@@ -978,4 +949,13 @@ public class PanZoomController
checkMainThread();
bounce();
}
+
+ public void setOverScrollMode(int overscrollMode) {
+ mX.setOverScrollMode(overscrollMode);
+ mY.setOverScrollMode(overscrollMode);
+ }
+
+ public int getOverScrollMode() {
+ return mX.getOverScrollMode();
+ }
}