Kaydet (Commit) f66ff689 authored tarafından Tomaž Vajngerl's avatar Tomaž Vajngerl

android: Improve panning smoothness (Fennec import)

Change-Id: I3983709651548eb97e588ebe2c2de608a4a4dfc7
üst 590fdf41
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
package org.mozilla.gecko.ui; package org.mozilla.gecko.ui;
import android.util.Log; import android.util.Log;
import android.view.View;
import org.json.JSONArray;
import org.mozilla.gecko.util.FloatUtils; import org.mozilla.gecko.util.FloatUtils;
import java.util.Map; import java.util.Map;
...@@ -23,7 +23,6 @@ abstract class Axis { ...@@ -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_SLOW = "ui.scrolling.friction_slow";
private static final String PREF_SCROLLING_FRICTION_FAST = "ui.scrolling.friction_fast"; 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_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_DECEL_RATE = "ui.scrolling.overscroll_decel_rate";
private static final String PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT = "ui.scrolling.overscroll_snap_limit"; private static final String PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT = "ui.scrolling.overscroll_snap_limit";
...@@ -59,22 +58,22 @@ abstract class Axis { ...@@ -59,22 +58,22 @@ abstract class Axis {
return (value == null || value < 0 ? defaultValue : value); return (value == null || value < 0 ? defaultValue : value);
} }
static void addPrefNames(JSONArray prefs) { static final float MS_PER_FRAME = 4.0f;
prefs.put(PREF_SCROLLING_FRICTION_FAST); private static final float FRAMERATE_MULTIPLIER = (1000f/60f) / MS_PER_FRAME;
prefs.put(PREF_SCROLLING_FRICTION_SLOW);
prefs.put(PREF_SCROLLING_VELOCITY_THRESHOLD); // The values we use for friction are based on a 16.6ms frame, adjust them to MS_PER_FRAME:
prefs.put(PREF_SCROLLING_MAX_EVENT_ACCELERATION); // FRICTION^1 = FRICTION_ADJUSTED^(16/MS_PER_FRAME)
prefs.put(PREF_SCROLLING_OVERSCROLL_DECEL_RATE); // FRICTION_ADJUSTED = e ^ ((ln(FRICTION))/FRAMERATE_MULTIPLIER)
prefs.put(PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT); static float getFrameAdjustedFriction(float baseFriction) {
prefs.put(PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE); return (float)Math.pow(Math.E, (Math.log(baseFriction) / FRAMERATE_MULTIPLIER));
} }
static void setPrefs(Map<String, Integer> prefs) { static void setPrefs(Map<String, Integer> prefs) {
FRICTION_SLOW = getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850); FRICTION_SLOW = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850));
FRICTION_FAST = getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970); FRICTION_FAST = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970));
VELOCITY_THRESHOLD = getIntPref(prefs, PREF_SCROLLING_VELOCITY_THRESHOLD, 10); VELOCITY_THRESHOLD = 10 / FRAMERATE_MULTIPLIER;
MAX_EVENT_ACCELERATION = getFloatPref(prefs, PREF_SCROLLING_MAX_EVENT_ACCELERATION, 12); 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); SNAP_LIMIT = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT, 300);
MIN_SCROLLABLE_DISTANCE = getFloatPref(prefs, PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE, 500); MIN_SCROLLABLE_DISTANCE = getFloatPref(prefs, PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE, 500);
Log.i(LOGTAG, "Prefs: " + FRICTION_SLOW + "," + FRICTION_FAST + "," + VELOCITY_THRESHOLD + "," Log.i(LOGTAG, "Prefs: " + FRICTION_SLOW + "," + FRICTION_FAST + "," + VELOCITY_THRESHOLD + ","
...@@ -86,9 +85,6 @@ abstract class Axis { ...@@ -86,9 +85,6 @@ abstract class Axis {
setPrefs(null); 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 { private enum FlingStates {
STOPPED, STOPPED,
PANNING, PANNING,
...@@ -104,6 +100,7 @@ abstract class Axis { ...@@ -104,6 +100,7 @@ abstract class Axis {
private final SubdocumentScrollHelper mSubscroller; 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 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 mTouchPos; /* Position of the most recent touch event on the current drag. */
private float mLastTouchPos; /* Position of the touch event before touchPos. */ private float mLastTouchPos; /* Position of the touch event before touchPos. */
...@@ -121,6 +118,15 @@ abstract class Axis { ...@@ -121,6 +118,15 @@ abstract class Axis {
Axis(SubdocumentScrollHelper subscroller) { Axis(SubdocumentScrollHelper subscroller) {
mSubscroller = 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() { private float getViewportEnd() {
...@@ -155,7 +161,7 @@ abstract class Axis { ...@@ -155,7 +161,7 @@ abstract class Axis {
// If there's a direction change, or current velocity is very low, // If there's a direction change, or current velocity is very low,
// allow setting of the velocity outright. Otherwise, use the current // allow setting of the velocity outright. Otherwise, use the current
// velocity and a maximum change factor to set the new velocity. // 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); boolean directionChange = (mVelocity > 0) != (newVelocity > 0);
if (curVelocityIsLow || (directionChange && !FloatUtils.fuzzyEquals(newVelocity, 0.0f))) { if (curVelocityIsLow || (directionChange && !FloatUtils.fuzzyEquals(newVelocity, 0.0f))) {
mVelocity = newVelocity; mVelocity = newVelocity;
...@@ -200,24 +206,31 @@ abstract class Axis { ...@@ -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 * 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. * 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 // 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. // apply to the top-level document) and only take into account axis locking.
if (mSubscroller.scrolling()) { if (mSubscroller.scrolling()) {
return !mScrollingDisabled; 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 * Returns the resistance, as a multiplier, that should be taken into account when
* tracking or pinching. * tracking or pinching.
*/ */
float getEdgeResistance() { float getEdgeResistance(boolean forPinching) {
float excess = getExcess(); 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 // excess can be greater than viewport length, but the resistance
// must never drop below 0.0 // must never drop below 0.0
return Math.max(0.0f, SNAP_LIMIT - excess / getViewportLength()); return Math.max(0.0f, SNAP_LIMIT - excess / getViewportLength());
...@@ -257,7 +270,15 @@ abstract class Axis { ...@@ -257,7 +270,15 @@ abstract class Axis {
} }
float excess = getExcess(); 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 we aren't overscrolled, just apply friction.
if (Math.abs(mVelocity) >= VELOCITY_THRESHOLD) { if (Math.abs(mVelocity) >= VELOCITY_THRESHOLD) {
mVelocity *= FRICTION_FAST; mVelocity *= FRICTION_FAST;
...@@ -268,7 +289,7 @@ abstract class Axis { ...@@ -268,7 +289,7 @@ abstract class Axis {
} else { } else {
// Otherwise, decrease the velocity linearly. // Otherwise, decrease the velocity linearly.
float elasticity = 1.0f - excess / (getViewportLength() * SNAP_LIMIT); 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); mVelocity = Math.min((mVelocity + OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
} else { // must be Overscroll.PLUS } else { // must be Overscroll.PLUS
mVelocity = Math.max((mVelocity - OVERSCROLL_DECEL_RATE) * elasticity, 0.0f); mVelocity = Math.max((mVelocity - OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
...@@ -285,14 +306,27 @@ abstract class Axis { ...@@ -285,14 +306,27 @@ abstract class Axis {
// Performs displacement of the viewport position according to the current velocity. // Performs displacement of the viewport position according to the current velocity.
void displace() { void displace() {
if (!scrollable()) { // if this isn't scrollable just return
if (!scrollable())
return; return;
}
if (mFlingState == FlingStates.PANNING) if (mFlingState == FlingStates.PANNING)
mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance(); mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance(false);
else else
mDisplacement += mVelocity; 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() { float resetDisplacement() {
......
...@@ -19,8 +19,6 @@ import org.mozilla.gecko.gfx.ImmutableViewportMetrics; ...@@ -19,8 +19,6 @@ import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
import org.mozilla.gecko.gfx.ViewportMetrics; import org.mozilla.gecko.gfx.ViewportMetrics;
import org.mozilla.gecko.util.FloatUtils; import org.mozilla.gecko.util.FloatUtils;
import java.util.Arrays;
import java.util.StringTokenizer;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
...@@ -53,26 +51,6 @@ public class PanZoomController ...@@ -53,26 +51,6 @@ public class PanZoomController
// The maximum amount we allow you to zoom into a page // The maximum amount we allow you to zoom into a page
private static final float MAX_ZOOM = 4.0f; 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 { private enum PanZoomState {
NOTHING, /* no touch-start events received */ NOTHING, /* no touch-start events received */
FLING, /* all touches removed, but we're still scrolling page */ FLING, /* all touches removed, but we're still scrolling page */
...@@ -125,6 +103,13 @@ public class PanZoomController ...@@ -125,6 +103,13 @@ public class PanZoomController
mSubscroller.destroy(); 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) { private void setState(PanZoomState state) {
mState = state; mState = state;
} }
...@@ -145,23 +130,6 @@ public class PanZoomController ...@@ -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) { public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) { switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: return onTouchStart(event); case MotionEvent.ACTION_DOWN: return onTouchStart(event);
...@@ -357,6 +325,8 @@ public class PanZoomController ...@@ -357,6 +325,8 @@ public class PanZoomController
} }
private boolean onTouchCancel(MotionEvent event) { private boolean onTouchCancel(MotionEvent event) {
cancelTouch();
if (mState == PanZoomState.WAITING_LISTENERS) { if (mState == PanZoomState.WAITING_LISTENERS) {
// we might get a cancel event from the TouchEventHandler while in the // we might get a cancel event from the TouchEventHandler while in the
// WAITING_LISTENERS state if the touch listeners prevent-default the // WAITING_LISTENERS state if the touch listeners prevent-default the
...@@ -367,7 +337,6 @@ public class PanZoomController ...@@ -367,7 +337,6 @@ public class PanZoomController
return false; return false;
} }
cancelTouch();
// ensure we snap back if we're overscrolled // ensure we snap back if we're overscrolled
bounce(); bounce();
return false; return false;
...@@ -392,7 +361,9 @@ public class PanZoomController ...@@ -392,7 +361,9 @@ public class PanZoomController
mY.startTouch(y); mY.startTouch(y);
mLastEventTime = time; 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); mY.setScrollingDisabled(true);
setState(PanZoomState.PANNING_LOCKED); setState(PanZoomState.PANNING_LOCKED);
} else if (Math.abs(angle - (Math.PI / 2)) < AXIS_LOCK_ANGLE) { } else if (Math.abs(angle - (Math.PI / 2)) < AXIS_LOCK_ANGLE) {
...@@ -507,7 +478,7 @@ public class PanZoomController ...@@ -507,7 +478,7 @@ public class PanZoomController
mAnimationTimer.scheduleAtFixedRate(new TimerTask() { mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
@Override @Override
public void run() { mTarget.post(runnable); } public void run() { mTarget.post(runnable); }
}, 0, 1000L/60L); }, 0, (int)Axis.MS_PER_FRAME);
} }
/* Stops the fling or bounce animation. */ /* Stops the fling or bounce animation. */
...@@ -607,7 +578,7 @@ public class PanZoomController ...@@ -607,7 +578,7 @@ public class PanZoomController
} }
/* Perform the next frame of the bounce-back animation. */ /* Perform the next frame of the bounce-back animation. */
if (mBounceFrame < ZOOM_ANIMATION_FRAMES.length) { if (mBounceFrame < (int)(256f/Axis.MS_PER_FRAME)) {
advanceBounce(); advanceBounce();
return; return;
} }
...@@ -621,7 +592,7 @@ public class PanZoomController ...@@ -621,7 +592,7 @@ public class PanZoomController
/* Performs one frame of a bounce animation. */ /* Performs one frame of a bounce animation. */
private void advanceBounce() { private void advanceBounce() {
synchronized (mTarget.getLock()) { synchronized (mTarget.getLock()) {
float t = ZOOM_ANIMATION_FRAMES[mBounceFrame]; float t = easeOut(mBounceFrame * Axis.MS_PER_FRAME / 256f);
ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t); ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
mTarget.setViewportMetrics(newMetrics); mTarget.setViewportMetrics(newMetrics);
mBounceFrame++; mBounceFrame++;
...@@ -818,7 +789,7 @@ public class PanZoomController ...@@ -818,7 +789,7 @@ public class PanZoomController
* Apply edge resistance if we're zoomed out smaller than the page size by scaling the zoom * Apply edge resistance if we're zoomed out smaller than the page size by scaling the zoom
* factor toward 1.0. * 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) if (spanRatio > 1.0f)
spanRatio = 1.0f + (spanRatio - 1.0f) * resistance; spanRatio = 1.0f + (spanRatio - 1.0f) * resistance;
else else
...@@ -978,4 +949,13 @@ public class PanZoomController ...@@ -978,4 +949,13 @@ public class PanZoomController
checkMainThread(); checkMainThread();
bounce(); bounce();
} }
public void setOverScrollMode(int overscrollMode) {
mX.setOverScrollMode(overscrollMode);
mY.setOverScrollMode(overscrollMode);
}
public int getOverScrollMode() {
return mX.getOverScrollMode();
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment