• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.content.Context;
20 import android.hardware.SensorManager;
21 import android.view.ViewConfiguration;
22 import android.view.animation.AnimationUtils;
23 import android.view.animation.Interpolator;
24 
25 
26 /**
27  * This class encapsulates scrolling.  The duration of the scroll
28  * can be passed in the constructor and specifies the maximum time that
29  * the scrolling animation should take.  Past this time, the scrolling is
30  * automatically moved to its final stage and computeScrollOffset()
31  * will always return false to indicate that scrolling is over.
32  */
33 public class Scroller  {
34     private int mMode;
35 
36     private int mStartX;
37     private int mStartY;
38     private int mFinalX;
39     private int mFinalY;
40 
41     private int mMinX;
42     private int mMaxX;
43     private int mMinY;
44     private int mMaxY;
45 
46     private int mCurrX;
47     private int mCurrY;
48     private long mStartTime;
49     private int mDuration;
50     private float mDurationReciprocal;
51     private float mDeltaX;
52     private float mDeltaY;
53     private float mViscousFluidScale;
54     private float mViscousFluidNormalize;
55     private boolean mFinished;
56     private Interpolator mInterpolator;
57 
58     private float mCoeffX = 0.0f;
59     private float mCoeffY = 1.0f;
60     private float mVelocity;
61 
62     private static final int DEFAULT_DURATION = 250;
63     private static final int SCROLL_MODE = 0;
64     private static final int FLING_MODE = 1;
65 
66     private final float mDeceleration;
67 
68     /**
69      * Create a Scroller with the default duration and interpolator.
70      */
Scroller(Context context)71     public Scroller(Context context) {
72         this(context, null);
73     }
74 
75     /**
76      * Create a Scroller with the specified interpolator. If the interpolator is
77      * null, the default (viscous) interpolator will be used.
78      */
Scroller(Context context, Interpolator interpolator)79     public Scroller(Context context, Interpolator interpolator) {
80         mFinished = true;
81         mInterpolator = interpolator;
82         float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
83         mDeceleration = SensorManager.GRAVITY_EARTH   // g (m/s^2)
84                       * 39.37f                        // inch/meter
85                       * ppi                           // pixels per inch
86                       * ViewConfiguration.getScrollFriction();
87     }
88 
89     /**
90      *
91      * Returns whether the scroller has finished scrolling.
92      *
93      * @return True if the scroller has finished scrolling, false otherwise.
94      */
isFinished()95     public final boolean isFinished() {
96         return mFinished;
97     }
98 
99     /**
100      * Force the finished field to a particular value.
101      *
102      * @param finished The new finished value.
103      */
forceFinished(boolean finished)104     public final void forceFinished(boolean finished) {
105         mFinished = finished;
106     }
107 
108     /**
109      * Returns how long the scroll event will take, in milliseconds.
110      *
111      * @return The duration of the scroll in milliseconds.
112      */
getDuration()113     public final int getDuration() {
114         return mDuration;
115     }
116 
117     /**
118      * Returns the current X offset in the scroll.
119      *
120      * @return The new X offset as an absolute distance from the origin.
121      */
getCurrX()122     public final int getCurrX() {
123         return mCurrX;
124     }
125 
126     /**
127      * Returns the current Y offset in the scroll.
128      *
129      * @return The new Y offset as an absolute distance from the origin.
130      */
getCurrY()131     public final int getCurrY() {
132         return mCurrY;
133     }
134 
135     /**
136      * @hide
137      * Returns the current velocity.
138      *
139      * @return The original velocity less the deceleration. Result may be
140      * negative.
141      */
getCurrVelocity()142     public float getCurrVelocity() {
143         return mVelocity - mDeceleration * timePassed() / 2000.0f;
144     }
145 
146     /**
147      * Returns the start X offset in the scroll.
148      *
149      * @return The start X offset as an absolute distance from the origin.
150      */
getStartX()151     public final int getStartX() {
152         return mStartX;
153     }
154 
155     /**
156      * Returns the start Y offset in the scroll.
157      *
158      * @return The start Y offset as an absolute distance from the origin.
159      */
getStartY()160     public final int getStartY() {
161         return mStartY;
162     }
163 
164     /**
165      * Returns where the scroll will end. Valid only for "fling" scrolls.
166      *
167      * @return The final X offset as an absolute distance from the origin.
168      */
getFinalX()169     public final int getFinalX() {
170         return mFinalX;
171     }
172 
173     /**
174      * Returns where the scroll will end. Valid only for "fling" scrolls.
175      *
176      * @return The final Y offset as an absolute distance from the origin.
177      */
getFinalY()178     public final int getFinalY() {
179         return mFinalY;
180     }
181 
182     /**
183      * Call this when you want to know the new location.  If it returns true,
184      * the animation is not yet finished.  loc will be altered to provide the
185      * new location.
186      */
computeScrollOffset()187     public boolean computeScrollOffset() {
188         if (mFinished) {
189             return false;
190         }
191 
192         int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
193 
194         if (timePassed < mDuration) {
195             switch (mMode) {
196             case SCROLL_MODE:
197                 float x = (float)timePassed * mDurationReciprocal;
198 
199                 if (mInterpolator == null)
200                     x = viscousFluid(x);
201                 else
202                     x = mInterpolator.getInterpolation(x);
203 
204                 mCurrX = mStartX + Math.round(x * mDeltaX);
205                 mCurrY = mStartY + Math.round(x * mDeltaY);
206                 if ((mCurrX == mFinalX) && (mCurrY == mFinalY)) {
207                     mFinished = true;
208                 }
209                 break;
210             case FLING_MODE:
211                 float timePassedSeconds = timePassed / 1000.0f;
212                 float distance = (mVelocity * timePassedSeconds)
213                         - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
214 
215                 mCurrX = mStartX + Math.round(distance * mCoeffX);
216                 // Pin to mMinX <= mCurrX <= mMaxX
217                 mCurrX = Math.min(mCurrX, mMaxX);
218                 mCurrX = Math.max(mCurrX, mMinX);
219 
220                 mCurrY = mStartY + Math.round(distance * mCoeffY);
221                 // Pin to mMinY <= mCurrY <= mMaxY
222                 mCurrY = Math.min(mCurrY, mMaxY);
223                 mCurrY = Math.max(mCurrY, mMinY);
224 
225                 if (mCurrX == mFinalX && mCurrY == mFinalY) {
226                     mFinished = true;
227                 }
228 
229                 break;
230             }
231         }
232         else {
233             mCurrX = mFinalX;
234             mCurrY = mFinalY;
235             mFinished = true;
236         }
237         return true;
238     }
239 
240     /**
241      * Start scrolling by providing a starting point and the distance to travel.
242      * The scroll will use the default value of 250 milliseconds for the
243      * duration.
244      *
245      * @param startX Starting horizontal scroll offset in pixels. Positive
246      *        numbers will scroll the content to the left.
247      * @param startY Starting vertical scroll offset in pixels. Positive numbers
248      *        will scroll the content up.
249      * @param dx Horizontal distance to travel. Positive numbers will scroll the
250      *        content to the left.
251      * @param dy Vertical distance to travel. Positive numbers will scroll the
252      *        content up.
253      */
startScroll(int startX, int startY, int dx, int dy)254     public void startScroll(int startX, int startY, int dx, int dy) {
255         startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
256     }
257 
258     /**
259      * Start scrolling by providing a starting point and the distance to travel.
260      *
261      * @param startX Starting horizontal scroll offset in pixels. Positive
262      *        numbers will scroll the content to the left.
263      * @param startY Starting vertical scroll offset in pixels. Positive numbers
264      *        will scroll the content up.
265      * @param dx Horizontal distance to travel. Positive numbers will scroll the
266      *        content to the left.
267      * @param dy Vertical distance to travel. Positive numbers will scroll the
268      *        content up.
269      * @param duration Duration of the scroll in milliseconds.
270      */
startScroll(int startX, int startY, int dx, int dy, int duration)271     public void startScroll(int startX, int startY, int dx, int dy, int duration) {
272         mMode = SCROLL_MODE;
273         mFinished = false;
274         mDuration = duration;
275         mStartTime = AnimationUtils.currentAnimationTimeMillis();
276         mStartX = startX;
277         mStartY = startY;
278         mFinalX = startX + dx;
279         mFinalY = startY + dy;
280         mDeltaX = dx;
281         mDeltaY = dy;
282         mDurationReciprocal = 1.0f / (float) mDuration;
283         // This controls the viscous fluid effect (how much of it)
284         mViscousFluidScale = 8.0f;
285         // must be set to 1.0 (used in viscousFluid())
286         mViscousFluidNormalize = 1.0f;
287         mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
288     }
289 
290     /**
291      * Start scrolling based on a fling gesture. The distance travelled will
292      * depend on the initial velocity of the fling.
293      *
294      * @param startX Starting point of the scroll (X)
295      * @param startY Starting point of the scroll (Y)
296      * @param velocityX Initial velocity of the fling (X) measured in pixels per
297      *        second.
298      * @param velocityY Initial velocity of the fling (Y) measured in pixels per
299      *        second
300      * @param minX Minimum X value. The scroller will not scroll past this
301      *        point.
302      * @param maxX Maximum X value. The scroller will not scroll past this
303      *        point.
304      * @param minY Minimum Y value. The scroller will not scroll past this
305      *        point.
306      * @param maxY Maximum Y value. The scroller will not scroll past this
307      *        point.
308      */
fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)309     public void fling(int startX, int startY, int velocityX, int velocityY,
310             int minX, int maxX, int minY, int maxY) {
311         mMode = FLING_MODE;
312         mFinished = false;
313 
314         float velocity = (float)Math.hypot(velocityX, velocityY);
315 
316         mVelocity = velocity;
317         mDuration = (int) (1000 * velocity / mDeceleration); // Duration is in
318                                                             // milliseconds
319         mStartTime = AnimationUtils.currentAnimationTimeMillis();
320         mStartX = startX;
321         mStartY = startY;
322 
323         mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity;
324         mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity;
325 
326         int totalDistance = (int) ((velocity * velocity) / (2 * mDeceleration));
327 
328         mMinX = minX;
329         mMaxX = maxX;
330         mMinY = minY;
331         mMaxY = maxY;
332 
333 
334         mFinalX = startX + Math.round(totalDistance * mCoeffX);
335         // Pin to mMinX <= mFinalX <= mMaxX
336         mFinalX = Math.min(mFinalX, mMaxX);
337         mFinalX = Math.max(mFinalX, mMinX);
338 
339         mFinalY = startY + Math.round(totalDistance * mCoeffY);
340         // Pin to mMinY <= mFinalY <= mMaxY
341         mFinalY = Math.min(mFinalY, mMaxY);
342         mFinalY = Math.max(mFinalY, mMinY);
343     }
344 
345 
346 
viscousFluid(float x)347     private float viscousFluid(float x)
348     {
349         x *= mViscousFluidScale;
350         if (x < 1.0f) {
351             x -= (1.0f - (float)Math.exp(-x));
352         } else {
353             float start = 0.36787944117f;   // 1/e == exp(-1)
354             x = 1.0f - (float)Math.exp(1.0f - x);
355             x = start + x * (1.0f - start);
356         }
357         x *= mViscousFluidNormalize;
358         return x;
359     }
360 
361     /**
362      * Stops the animation. Contrary to {@link #forceFinished(boolean)},
363      * aborting the animating cause the scroller to move to the final x and y
364      * position
365      *
366      * @see #forceFinished(boolean)
367      */
abortAnimation()368     public void abortAnimation() {
369         mCurrX = mFinalX;
370         mCurrY = mFinalY;
371         mFinished = true;
372     }
373 
374     /**
375      * Extend the scroll animation. This allows a running animation to scroll
376      * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
377      *
378      * @param extend Additional time to scroll in milliseconds.
379      * @see #setFinalX(int)
380      * @see #setFinalY(int)
381      */
extendDuration(int extend)382     public void extendDuration(int extend) {
383         int passed = timePassed();
384         mDuration = passed + extend;
385         mDurationReciprocal = 1.0f / (float)mDuration;
386         mFinished = false;
387     }
388 
389     /**
390      * Returns the time elapsed since the beginning of the scrolling.
391      *
392      * @return The elapsed time in milliseconds.
393      */
timePassed()394     public int timePassed() {
395         return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
396     }
397 
398     /**
399      * Sets the final position (X) for this scroller.
400      *
401      * @param newX The new X offset as an absolute distance from the origin.
402      * @see #extendDuration(int)
403      * @see #setFinalY(int)
404      */
setFinalX(int newX)405     public void setFinalX(int newX) {
406         mFinalX = newX;
407         mDeltaX = mFinalX - mStartX;
408         mFinished = false;
409     }
410 
411     /**
412      * Sets the final position (Y) for this scroller.
413      *
414      * @param newY The new Y offset as an absolute distance from the origin.
415      * @see #extendDuration(int)
416      * @see #setFinalX(int)
417      */
setFinalY(int newY)418     public void setFinalY(int newY) {
419         mFinalY = newY;
420         mDeltaY = mFinalY - mStartY;
421         mFinished = false;
422     }
423 }
424