summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.xml14
-rw-r--r--core/java/android/widget/AbsListView.java129
-rw-r--r--core/java/android/widget/Scroller.java101
3 files changed, 182 insertions, 62 deletions
diff --git a/api/current.xml b/api/current.xml
index 7688c08bb5ff..a66acbc8d2f4 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -239966,6 +239966,20 @@
<parameter name="interpolator" type="android.view.animation.Interpolator">
</parameter>
</constructor>
+<constructor name="Scroller"
+ type="android.widget.Scroller"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="interpolator" type="android.view.animation.Interpolator">
+</parameter>
+<parameter name="flywheel" type="boolean">
+</parameter>
+</constructor>
<method name="abortAnimation"
return="void"
abstract="false"
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 466e54135e65..ffe78e713d4b 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2543,6 +2543,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mMotionCorrection = 0;
motionPosition = findMotionRow(y);
reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+ mFlingRunnable.flywheelTouch();
}
}
}
@@ -2715,6 +2716,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
} else {
mTouchMode = TOUCH_MODE_REST;
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+ if (mFlingRunnable != null) {
+ mFlingRunnable.endFling();
+ }
}
}
} else {
@@ -2758,7 +2762,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
case MotionEvent.ACTION_CANCEL: {
mTouchMode = TOUCH_MODE_REST;
setPressed(false);
- View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
+ View motionView = getChildAt(mMotionPosition - mFirstPosition);
if (motionView != null) {
motionView.setPressed(false);
}
@@ -2946,6 +2950,30 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
private int mLastFlingY;
+ private final Runnable mCheckFlywheel = new Runnable() {
+ public void run() {
+ final int activeId = mActivePointerId;
+ final VelocityTracker vt = mVelocityTracker;
+ final Scroller scroller = mScroller;
+ if (vt == null || activeId == INVALID_POINTER) {
+ return;
+ }
+
+ vt.computeCurrentVelocity(1000, mMaximumVelocity);
+ final float yvel = -vt.getYVelocity(activeId);
+
+ if (scroller.isScrollingInDirection(0, yvel)) {
+ // Keep the fling alive a little longer
+ postDelayed(this, FLYWHEEL_TIMEOUT);
+ } else {
+ endFling();
+ mTouchMode = TOUCH_MODE_SCROLL;
+ }
+ }
+ };
+
+ private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
+
FlingRunnable() {
mScroller = new Scroller(getContext());
}
@@ -2978,73 +3006,85 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
post(this);
}
- private void endFling() {
+ void endFling() {
mTouchMode = TOUCH_MODE_REST;
removeCallbacks(this);
+ removeCallbacks(mCheckFlywheel);
if (mPositionScroller != null) {
removeCallbacks(mPositionScroller);
}
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
clearScrollingCache();
+ mScroller.abortAnimation();
+ }
+
+ void flywheelTouch() {
+ postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
}
public void run() {
switch (mTouchMode) {
default:
+ endFling();
return;
- case TOUCH_MODE_FLING: {
+ case TOUCH_MODE_SCROLL:
+ if (mScroller.isFinished()) {
+ return;
+ }
+ // Fall through
+ case TOUCH_MODE_FLING:
if (mItemCount == 0 || getChildCount() == 0) {
endFling();
return;
}
+ break;
+ }
+ final Scroller scroller = mScroller;
+ boolean more = scroller.computeScrollOffset();
+ final int y = scroller.getCurrY();
- final Scroller scroller = mScroller;
- boolean more = scroller.computeScrollOffset();
- final int y = scroller.getCurrY();
-
- // Flip sign to convert finger direction to list items direction
- // (e.g. finger moving down means list is moving towards the top)
- int delta = mLastFlingY - y;
-
- // Pretend that each frame of a fling scroll is a touch scroll
- if (delta > 0) {
- // List is moving towards the top. Use first view as mMotionPosition
- mMotionPosition = mFirstPosition;
- final View firstView = getChildAt(0);
- mMotionViewOriginalTop = firstView.getTop();
-
- // Don't fling more than 1 screen
- delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
- } else {
- // List is moving towards the bottom. Use last view as mMotionPosition
- int offsetToLast = getChildCount() - 1;
- mMotionPosition = mFirstPosition + offsetToLast;
+ // Flip sign to convert finger direction to list items direction
+ // (e.g. finger moving down means list is moving towards the top)
+ int delta = mLastFlingY - y;
- final View lastView = getChildAt(offsetToLast);
- mMotionViewOriginalTop = lastView.getTop();
+ // Pretend that each frame of a fling scroll is a touch scroll
+ if (delta > 0) {
+ // List is moving towards the top. Use first view as mMotionPosition
+ mMotionPosition = mFirstPosition;
+ final View firstView = getChildAt(0);
+ mMotionViewOriginalTop = firstView.getTop();
- // Don't fling more than 1 screen
- delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
- }
+ // Don't fling more than 1 screen
+ delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
+ } else {
+ // List is moving towards the bottom. Use last view as mMotionPosition
+ int offsetToLast = getChildCount() - 1;
+ mMotionPosition = mFirstPosition + offsetToLast;
- // Don't stop just because delta is zero (it could have been rounded)
- final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0);
+ final View lastView = getChildAt(offsetToLast);
+ mMotionViewOriginalTop = lastView.getTop();
- if (more && !atEnd) {
- invalidate();
- mLastFlingY = y;
- post(this);
- } else {
- endFling();
-
- if (PROFILE_FLINGING) {
- if (mFlingProfilingStarted) {
- Debug.stopMethodTracing();
- mFlingProfilingStarted = false;
- }
+ // Don't fling more than 1 screen
+ delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
+ }
+
+ // Don't stop just because delta is zero (it could have been rounded)
+ final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0);
+
+ if (more && !atEnd) {
+ invalidate();
+ mLastFlingY = y;
+ post(this);
+ } else {
+ endFling();
+
+ if (PROFILE_FLINGING) {
+ if (mFlingProfilingStarted) {
+ Debug.stopMethodTracing();
+ mFlingProfilingStarted = false;
}
if (mFlingStrictSpan != null) {
@@ -3052,10 +3092,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mFlingStrictSpan = null;
}
}
- break;
}
- }
-
}
}
diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java
index 79ab44890af2..3f2bd25d3dc1 100644
--- a/core/java/android/widget/Scroller.java
+++ b/core/java/android/widget/Scroller.java
@@ -18,6 +18,8 @@ package android.widget;
import android.content.Context;
import android.hardware.SensorManager;
+import android.os.Build;
+import android.util.FloatMath;
import android.view.ViewConfiguration;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -54,6 +56,7 @@ public class Scroller {
private float mViscousFluidNormalize;
private boolean mFinished;
private Interpolator mInterpolator;
+ private boolean mFlywheel;
private float mCoeffX = 0.0f;
private float mCoeffY = 1.0f;
@@ -63,9 +66,37 @@ public class Scroller {
private static final int SCROLL_MODE = 0;
private static final int FLING_MODE = 1;
+ private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
+ private static float ALPHA = 400; // pixels / seconds
+ private static float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
+ private static float END_TENSION = 1.0f - START_TENSION;
+ private static final int NB_SAMPLES = 100;
+ private static final float[] SPLINE = new float[NB_SAMPLES + 1];
+
+
private float mDeceleration;
private final float mPpi;
+ static {
+ float x_min = 0.0f;
+ for (int i = 0; i <= NB_SAMPLES; i++) {
+ final float t = (float) i / NB_SAMPLES;
+ float x_max = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = x_min + (x_max - x_min) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
+ if (Math.abs(tx - t) < 1E-5) break;
+ if (tx > t) x_max = x;
+ else x_min = x;
+ }
+ final float d = coef + x * x * x;
+ SPLINE[i] = d;
+ }
+ SPLINE[NB_SAMPLES] = 1.0f;
+ }
+
/**
* Create a Scroller with the default duration and interpolator.
*/
@@ -73,22 +104,28 @@ public class Scroller {
this(context, null);
}
+ public Scroller(Context context, Interpolator interpolator) {
+ this(context, interpolator,
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
+ }
+
/**
* Create a Scroller with the specified interpolator. If the interpolator is
* null, the default (viscous) interpolator will be used.
*/
- public Scroller(Context context, Interpolator interpolator) {
+ public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
mFinished = true;
mInterpolator = interpolator;
mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
+ mFlywheel = flywheel;
}
/**
* The amount of friction applied to flings. The default value
* is {@link ViewConfiguration#getScrollFriction}.
*
- * @return A scalar dimensionless value representing the coefficient of
+ * @param friction A scalar dimension-less value representing the coefficient of
* friction.
*/
public final void setFriction(float friction) {
@@ -210,7 +247,7 @@ public class Scroller {
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
- float x = (float)timePassed * mDurationReciprocal;
+ float x = timePassed * mDurationReciprocal;
if (mInterpolator == null)
x = viscousFluid(x);
@@ -221,16 +258,20 @@ public class Scroller {
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
- float timePassedSeconds = timePassed / 1000.0f;
- float distance = (mVelocity * timePassedSeconds)
- - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
+ final float t = (float) timePassed / mDuration;
+ final int index = (int) (NB_SAMPLES * t);
+ final float t_inf = (float) index / NB_SAMPLES;
+ final float t_sup = (float) (index + 1) / NB_SAMPLES;
+ final float d_inf = SPLINE[index];
+ final float d_sup = SPLINE[index + 1];
+ final float distanceCoef = d_inf + (t - t_inf) / (t_sup - t_inf) * (d_sup - d_inf);
- mCurrX = mStartX + Math.round(distance * mCoeffX);
+ mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
// Pin to mMinX <= mCurrX <= mMaxX
mCurrX = Math.min(mCurrX, mMaxX);
mCurrX = Math.max(mCurrX, mMinX);
- mCurrY = mStartY + Math.round(distance * mCoeffY);
+ mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY);
@@ -292,7 +333,7 @@ public class Scroller {
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
- mDurationReciprocal = 1.0f / (float) mDuration;
+ mDurationReciprocal = 1.0f / mDuration;
// This controls the viscous fluid effect (how much of it)
mViscousFluidScale = 8.0f;
// must be set to 1.0 (used in viscousFluid())
@@ -321,14 +362,34 @@ public class Scroller {
*/
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY) {
+ // Continue a scroll or fling in progress
+ if (mFlywheel && !mFinished) {
+ float oldVel = getCurrVelocity();
+
+ float dx = (float) (mFinalX - mStartX);
+ float dy = (float) (mFinalY - mStartY);
+ float hyp = FloatMath.sqrt(dx * dx + dy * dy);
+
+ float ndx = dx / hyp;
+ float ndy = dy / hyp;
+
+ float oldVelocityX = ndx * oldVel;
+ float oldVelocityY = ndy * oldVel;
+ if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
+ Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+ velocityX += oldVelocityX;
+ velocityY += oldVelocityY;
+ }
+ }
+
mMode = FLING_MODE;
mFinished = false;
- float velocity = (float)Math.hypot(velocityX, velocityY);
+ float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
mVelocity = velocity;
- mDuration = (int) (1000 * velocity / mDeceleration); // Duration is in
- // milliseconds
+ final double l = Math.log(START_TENSION * velocity / ALPHA);
+ mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
@@ -336,14 +397,14 @@ public class Scroller {
mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity;
mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity;
- int totalDistance = (int) ((velocity * velocity) / (2 * mDeceleration));
+ int totalDistance =
+ (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
mMinX = minX;
mMaxX = maxX;
mMinY = minY;
mMaxY = maxY;
-
-
+
mFinalX = startX + Math.round(totalDistance * mCoeffX);
// Pin to mMinX <= mFinalX <= mMaxX
mFinalX = Math.min(mFinalX, mMaxX);
@@ -395,7 +456,7 @@ public class Scroller {
public void extendDuration(int extend) {
int passed = timePassed();
mDuration = passed + extend;
- mDurationReciprocal = 1.0f / (float)mDuration;
+ mDurationReciprocal = 1.0f / mDuration;
mFinished = false;
}
@@ -433,4 +494,12 @@ public class Scroller {
mDeltaY = mFinalY - mStartY;
mFinished = false;
}
+
+ /**
+ * @hide
+ */
+ public boolean isScrollingInDirection(float xvel, float yvel) {
+ return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
+ Math.signum(yvel) == Math.signum(mFinalY - mStartY);
+ }
}