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 @@
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() {
......
......@@ -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();
}
}
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