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