• 【Android】View的滚动——Scroller


    当一个View超出我们屏幕大小的时候,肯定只能显示View的一部分,这个时候必然涉及到移动一个View。在View的源码中,有两个这样子的方法:

     1  /**
     2      * Set the scrolled position of your view. This will cause a call to
     3      * {@link #onScrollChanged(int, int, int, int)} and the view will be
     4      * invalidated.
     5      * @param x the x position to scroll to
     6      * @param y the y position to scroll to
     7      */
     8     public void scrollTo(int x, int y) {
     9         if (mScrollX != x || mScrollY != y) {
    10             int oldX = mScrollX;
    11             int oldY = mScrollY;
    12             mScrollX = x;
    13             mScrollY = y;
    14             onScrollChanged(mScrollX, mScrollY, oldX, oldY);
    15             if (!awakenScrollBars()) {
    16                 invalidate();
    17             }
    18         }
    19     }
    20 
    21     /**
    22      * Move the scrolled position of your view. This will cause a call to
    23      * {@link #onScrollChanged(int, int, int, int)} and the view will be
    24      * invalidated.
    25      * @param x the amount of pixels to scroll by horizontally
    26      * @param y the amount of pixels to scroll by vertically
    27      */
    28     public void scrollBy(int x, int y) {
    29         scrollTo(mScrollX + x, mScrollY + y);
    30     }

    归根结底,这两个方法调用的都是下面这个方法:

     1     /**
     2      * This is called in response to an internal scroll in this view (i.e., the
     3      * view scrolled its own contents). This is typically as a result of
     4      * {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
     5      * called.
     6      *
     7      * @param l Current horizontal scroll origin.
     8      * @param t Current vertical scroll origin.
     9      * @param oldl Previous horizontal scroll origin.
    10      * @param oldt Previous vertical scroll origin.
    11      */
    12     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    13         mBackgroundSizeChanged = true;
    14 
    15         final AttachInfo ai = mAttachInfo;
    16         if (ai != null) {
    17             ai.mViewScrollChanged = true;
    18         }
    19     }

    这里具体发生了什么事情,不得而知,但是我们可以基本认为:View是提供给外部滚动自己内容的方式的(好吧,这里弱爆了。。。)

    当我们去滚动一个View的时候,我们有两种方式去滚动一个View,第一种是瞬间移动到我们想要移动到的地方,第二种则是,以动画方式移动,并且设定一定的时长。当然第二种方式更加用户友好。我们需要手动控制这个过程。时刻了解一些信息,比如:

    1)滚动过了多长时间;

    2)现在滚动了多少距离了;

    3)滚动是否结束了;

    在Android中有一个类,其源码如下:

      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      */
     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      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */ 
    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                 break;
    207             case FLING_MODE:
    208                 float timePassedSeconds = timePassed / 1000.0f;
    209                 float distance = (mVelocity * timePassedSeconds)
    210                         - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
    211                 
    212                 mCurrX = mStartX + Math.round(distance * mCoeffX);
    213                 // Pin to mMinX <= mCurrX <= mMaxX
    214                 mCurrX = Math.min(mCurrX, mMaxX);
    215                 mCurrX = Math.max(mCurrX, mMinX);
    216                 
    217                 mCurrY = mStartY + Math.round(distance * mCoeffY);
    218                 // Pin to mMinY <= mCurrY <= mMaxY
    219                 mCurrY = Math.min(mCurrY, mMaxY);
    220                 mCurrY = Math.max(mCurrY, mMinY);
    221                 
    222                 break;
    223             }
    224         }
    225         else {
    226             mCurrX = mFinalX;
    227             mCurrY = mFinalY;
    228             mFinished = true;
    229         }
    230         return true;
    231     }
    232     
    233     /**
    234      * Start scrolling by providing a starting point and the distance to travel.
    235      * The scroll will use the default value of 250 milliseconds for the
    236      * duration.
    237      * 
    238      * @param startX Starting horizontal scroll offset in pixels. Positive
    239      *        numbers will scroll the content to the left.
    240      * @param startY Starting vertical scroll offset in pixels. Positive numbers
    241      *        will scroll the content up.
    242      * @param dx Horizontal distance to travel. Positive numbers will scroll the
    243      *        content to the left.
    244      * @param dy Vertical distance to travel. Positive numbers will scroll the
    245      *        content up.
    246      */
    247     public void startScroll(int startX, int startY, int dx, int dy) {
    248         startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
    249     }
    250 
    251     /**
    252      * Start scrolling by providing a starting point and the distance to travel.
    253      * 
    254      * @param startX Starting horizontal scroll offset in pixels. Positive
    255      *        numbers will scroll the content to the left.
    256      * @param startY Starting vertical scroll offset in pixels. Positive numbers
    257      *        will scroll the content up.
    258      * @param dx Horizontal distance to travel. Positive numbers will scroll the
    259      *        content to the left.
    260      * @param dy Vertical distance to travel. Positive numbers will scroll the
    261      *        content up.
    262      * @param duration Duration of the scroll in milliseconds.
    263      */
    264     public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    265         mMode = SCROLL_MODE;
    266         mFinished = false;
    267         mDuration = duration;
    268         mStartTime = AnimationUtils.currentAnimationTimeMillis();
    269         mStartX = startX;
    270         mStartY = startY;
    271         mFinalX = startX + dx;
    272         mFinalY = startY + dy;
    273         mDeltaX = dx;
    274         mDeltaY = dy;
    275         mDurationReciprocal = 1.0f / (float) mDuration;
    276         // This controls the viscous fluid effect (how much of it)
    277         mViscousFluidScale = 8.0f;
    278         // must be set to 1.0 (used in viscousFluid())
    279         mViscousFluidNormalize = 1.0f;
    280         mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
    281     }
    282 
    283     /**
    284      * Start scrolling based on a fling gesture. The distance travelled will
    285      * depend on the initial velocity of the fling.
    286      * 
    287      * @param startX Starting point of the scroll (X)
    288      * @param startY Starting point of the scroll (Y)
    289      * @param velocityX Initial velocity of the fling (X) measured in pixels per
    290      *        second.
    291      * @param velocityY Initial velocity of the fling (Y) measured in pixels per
    292      *        second
    293      * @param minX Minimum X value. The scroller will not scroll past this
    294      *        point.
    295      * @param maxX Maximum X value. The scroller will not scroll past this
    296      *        point.
    297      * @param minY Minimum Y value. The scroller will not scroll past this
    298      *        point.
    299      * @param maxY Maximum Y value. The scroller will not scroll past this
    300      *        point.
    301      */
    302     public void fling(int startX, int startY, int velocityX, int velocityY,
    303             int minX, int maxX, int minY, int maxY) {
    304         mMode = FLING_MODE;
    305         mFinished = false;
    306 
    307         float velocity = (float)Math.hypot(velocityX, velocityY);
    308      
    309         mVelocity = velocity;
    310         mDuration = (int) (1000 * velocity / mDeceleration); // Duration is in
    311                                                             // milliseconds
    312         mStartTime = AnimationUtils.currentAnimationTimeMillis();
    313         mStartX = startX;
    314         mStartY = startY;
    315 
    316         mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity; 
    317         mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity;
    318 
    319         int totalDistance = (int) ((velocity * velocity) / (2 * mDeceleration));
    320         
    321         mMinX = minX;
    322         mMaxX = maxX;
    323         mMinY = minY;
    324         mMaxY = maxY;
    325         
    326         
    327         mFinalX = startX + Math.round(totalDistance * mCoeffX);
    328         // Pin to mMinX <= mFinalX <= mMaxX
    329         mFinalX = Math.min(mFinalX, mMaxX);
    330         mFinalX = Math.max(mFinalX, mMinX);
    331         
    332         mFinalY = startY + Math.round(totalDistance * mCoeffY);
    333         // Pin to mMinY <= mFinalY <= mMaxY
    334         mFinalY = Math.min(mFinalY, mMaxY);
    335         mFinalY = Math.max(mFinalY, mMinY);
    336     }
    337     
    338     
    339     
    340     private float viscousFluid(float x)
    341     {
    342         x *= mViscousFluidScale;
    343         if (x < 1.0f) {
    344             x -= (1.0f - (float)Math.exp(-x));
    345         } else {
    346             float start = 0.36787944117f;   // 1/e == exp(-1)
    347             x = 1.0f - (float)Math.exp(1.0f - x);
    348             x = start + x * (1.0f - start);
    349         }
    350         x *= mViscousFluidNormalize;
    351         return x;
    352     }
    353     
    354     /**
    355      * Stops the animation. Contrary to {@link #forceFinished(boolean)},
    356      * aborting the animating cause the scroller to move to the final x and y
    357      * position
    358      *
    359      * @see #forceFinished(boolean)
    360      */
    361     public void abortAnimation() {
    362         mCurrX = mFinalX;
    363         mCurrY = mFinalY;
    364         mFinished = true;
    365     }
    366     
    367     /**
    368      * Extend the scroll animation. This allows a running animation to scroll
    369      * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
    370      *
    371      * @param extend Additional time to scroll in milliseconds.
    372      * @see #setFinalX(int)
    373      * @see #setFinalY(int)
    374      */
    375     public void extendDuration(int extend) {
    376         int passed = timePassed();
    377         mDuration = passed + extend;
    378         mDurationReciprocal = 1.0f / (float)mDuration;
    379         mFinished = false;
    380     }
    381 
    382     /**
    383      * Returns the time elapsed since the beginning of the scrolling.
    384      *
    385      * @return The elapsed time in milliseconds.
    386      */
    387     public int timePassed() {
    388         return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    389     }
    390 
    391     /**
    392      * Sets the final position (X) for this scroller.
    393      *
    394      * @param newX The new X offset as an absolute distance from the origin.
    395      * @see #extendDuration(int)
    396      * @see #setFinalY(int)
    397      */
    398     public void setFinalX(int newX) {
    399         mFinalX = newX;
    400         mDeltaX = mFinalX - mStartX;
    401         mFinished = false;
    402     }
    403 
    404     /**
    405      * Sets the final position (Y) for this scroller.
    406      *
    407      * @param newY The new Y offset as an absolute distance from the origin.
    408      * @see #extendDuration(int)
    409      * @see #setFinalX(int)
    410      */
    411     public void setFinalY(int newY) {
    412         mFinalY = newY;
    413         mDeltaY = mFinalY - mStartY;
    414         mFinished = false;
    415     }
    416 }

    这个类可以精确记录我们滚动过程中的所有信息,并且通过它,我们可以实现相应的动画。下面解释这个类的几个重要方面:

    1)新建这个类的时候,可以传入加速器,这个加速器可以被用来计算滚动过程中的已经滚动的距离;

     1 /**
     2      * Create a Scroller with the specified interpolator. If the interpolator is
     3      * null, the default (viscous) interpolator will be used.
     4      */
     5     public Scroller(Context context, Interpolator interpolator) {
     6         mFinished = true;
     7         mInterpolator = interpolator;
     8         float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
     9         mDeceleration = SensorManager.GRAVITY_EARTH   // g (m/s^2)
    10                       * 39.37f                        // inch/meter
    11                       * ppi                           // pixels per inch
    12                       * ViewConfiguration.getScrollFriction();
    13     }

    上面是构造方法,其中有一个比较复杂的计算,源码中毫无注释,先不管;

    2)最重要的方法就是下面这个:

     1 /**
     2      * Call this when you want to know the new location.  If it returns true,
     3      * the animation is not yet finished.  loc will be altered to provide the
     4      * new location.
     5      */ 
     6     public boolean computeScrollOffset() {
     7         if (mFinished) {
     8             return false;
     9         }
    10 
    11         int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    12     
    13         if (timePassed < mDuration) {
    14             switch (mMode) {
    15             case SCROLL_MODE:
    16                 float x = (float)timePassed * mDurationReciprocal;
    17     
    18                 if (mInterpolator == null)
    19                     x = viscousFluid(x); 
    20                 else
    21                     x = mInterpolator.getInterpolation(x);
    22     
    23                 mCurrX = mStartX + Math.round(x * mDeltaX);
    24                 mCurrY = mStartY + Math.round(x * mDeltaY);
    25                 break;
    26             case FLING_MODE:
    27                 float timePassedSeconds = timePassed / 1000.0f;
    28                 float distance = (mVelocity * timePassedSeconds)
    29                         - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
    30                 
    31                 mCurrX = mStartX + Math.round(distance * mCoeffX);
    32                 // Pin to mMinX <= mCurrX <= mMaxX
    33                 mCurrX = Math.min(mCurrX, mMaxX);
    34                 mCurrX = Math.max(mCurrX, mMinX);
    35                 
    36                 mCurrY = mStartY + Math.round(distance * mCoeffY);
    37                 // Pin to mMinY <= mCurrY <= mMaxY
    38                 mCurrY = Math.min(mCurrY, mMaxY);
    39                 mCurrY = Math.max(mCurrY, mMinY);
    40                 
    41                 break;
    42             }
    43         }
    44         else {
    45             mCurrX = mFinalX;
    46             mCurrY = mFinalY;
    47             mFinished = true;
    48         }
    49         return true;
    50     }

    只要在滚动,也就是mFinished=false的时候,这个方法就会不停的计算当前应该处于的位置!

    3)第三个是启动滚动的类:

     1 /**
     2      * Start scrolling by providing a starting point and the distance to travel.
     3      * The scroll will use the default value of 250 milliseconds for the
     4      * duration.
     5      * 
     6      * @param startX Starting horizontal scroll offset in pixels. Positive
     7      *        numbers will scroll the content to the left.
     8      * @param startY Starting vertical scroll offset in pixels. Positive numbers
     9      *        will scroll the content up.
    10      * @param dx Horizontal distance to travel. Positive numbers will scroll the
    11      *        content to the left.
    12      * @param dy Vertical distance to travel. Positive numbers will scroll the
    13      *        content up.
    14      */
    15     public void startScroll(int startX, int startY, int dx, int dy) {
    16         startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
    17     }
    18 
    19     /**
    20      * Start scrolling by providing a starting point and the distance to travel.
    21      * 
    22      * @param startX Starting horizontal scroll offset in pixels. Positive
    23      *        numbers will scroll the content to the left.
    24      * @param startY Starting vertical scroll offset in pixels. Positive numbers
    25      *        will scroll the content up.
    26      * @param dx Horizontal distance to travel. Positive numbers will scroll the
    27      *        content to the left.
    28      * @param dy Vertical distance to travel. Positive numbers will scroll the
    29      *        content up.
    30      * @param duration Duration of the scroll in milliseconds.
    31      */
    32     public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    33         mMode = SCROLL_MODE;
    34         mFinished = false;
    35         mDuration = duration;
    36         mStartTime = AnimationUtils.currentAnimationTimeMillis();
    37         mStartX = startX;
    38         mStartY = startY;
    39         mFinalX = startX + dx;
    40         mFinalY = startY + dy;
    41         mDeltaX = dx;
    42         mDeltaY = dy;
    43         mDurationReciprocal = 1.0f / (float) mDuration;
    44         // This controls the viscous fluid effect (how much of it)
    45         mViscousFluidScale = 8.0f;
    46         // must be set to 1.0 (used in viscousFluid())
    47         mViscousFluidNormalize = 1.0f;
    48         mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
    49     }

    其实这个方法只是设置一些初始的参数,以便于用于计算。

    我想,之所以Scroller难以理解,其实是因为很多人以为Scroller类控制View的滚动,其实不是这个样子的!Scroller类只是当你告诉它你要在一定的时间(默认250ms)内将View滚动一定距离,任何时刻View存在的位置!具体的滚动,其实是View调用自己的ScrollTo和ScrollBy方法实现的!

    讲到这里,我想读者一定有一些想法了,假设我现在调用Scroller的startScroll方法,我就可实时获得该动画在任何时刻的位置,那么,假设我在View上要施加一个动画移动,那么我只要产生一个循环,不停去问Scroller  View应该存在的位置,然后将View移动到需要移动到的地方,自然就成为动画移动了!

    在View的源码中,有方法如下:

    1 /**
    2      * Called by a parent to request that a child update its values for mScrollX
    3      * and mScrollY if necessary. This will typically be done if the child is
    4      * animating a scroll using a {@link android.widget.Scroller Scroller}
    5      * object.
    6      */
    7     public void computeScroll() {
    8     }

    是个空方法,应该是被用来覆盖的。并且明确提出经典用法是通过Scroller方法来滚动一个view。OK,我们来看看到底如何实现上面的循环。现在我们先离开上面,来看看ViewGroup里面的一个方法:

      1 /**
      2      * Draw one child of this View Group. This method is responsible for getting
      3      * the canvas in the right state. This includes clipping, translating so
      4      * that the child's scrolled origin is at 0, 0, and applying any animation
      5      * transformations.
      6      *
      7      * @param canvas The canvas on which to draw the child
      8      * @param child Who to draw
      9      * @param drawingTime The time at which draw is occuring
     10      * @return True if an invalidate() was issued
     11      */
     12     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
     13         boolean more = false;
     14 
     15         final int cl = child.mLeft;
     16         final int ct = child.mTop;
     17         final int cr = child.mRight;
     18         final int cb = child.mBottom;
     19 
     20         final int flags = mGroupFlags;
     21 
     22         if ((flags & FLAG_CLEAR_TRANSFORMATION) == FLAG_CLEAR_TRANSFORMATION) {
     23             if (mChildTransformation != null) {
     24                 mChildTransformation.clear();
     25             }
     26             mGroupFlags &= ~FLAG_CLEAR_TRANSFORMATION;
     27         }
     28 
     29         Transformation transformToApply = null;
     30         final Animation a = child.getAnimation();
     31         boolean concatMatrix = false;
     32 
     33         if (a != null) {
     34             if (mInvalidateRegion == null) {
     35                 mInvalidateRegion = new RectF();
     36             }
     37             final RectF region = mInvalidateRegion;
     38 
     39             final boolean initialized = a.isInitialized();
     40             if (!initialized) {
     41                 a.initialize(cr - cl, cb - ct, getWidth(), getHeight());
     42                 a.initializeInvalidateRegion(0, 0, cr - cl, cb - ct);
     43                 child.onAnimationStart();
     44             }
     45 
     46             if (mChildTransformation == null) {
     47                 mChildTransformation = new Transformation();
     48             }
     49             more = a.getTransformation(drawingTime, mChildTransformation);
     50             transformToApply = mChildTransformation;
     51 
     52             concatMatrix = a.willChangeTransformationMatrix();
     53 
     54             if (more) {
     55                 if (!a.willChangeBounds()) {
     56                     if ((flags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) ==
     57                             FLAG_OPTIMIZE_INVALIDATE) {
     58                         mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
     59                     } else if ((flags & FLAG_INVALIDATE_REQUIRED) == 0) {
     60                         // The child need to draw an animation, potentially offscreen, so
     61                         // make sure we do not cancel invalidate requests
     62                         mPrivateFlags |= DRAW_ANIMATION;
     63                         invalidate(cl, ct, cr, cb);
     64                     }
     65                 } else {
     66                     a.getInvalidateRegion(0, 0, cr - cl, cb - ct, region, transformToApply);
     67 
     68                     // The child need to draw an animation, potentially offscreen, so
     69                     // make sure we do not cancel invalidate requests
     70                     mPrivateFlags |= DRAW_ANIMATION;
     71 
     72                     final int left = cl + (int) region.left;
     73                     final int top = ct + (int) region.top;
     74                     invalidate(left, top, left + (int) region.width(), top + (int) region.height());
     75                 }
     76             }
     77         } else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==
     78                 FLAG_SUPPORT_STATIC_TRANSFORMATIONS) {
     79             if (mChildTransformation == null) {
     80                 mChildTransformation = new Transformation();
     81             }
     82             final boolean hasTransform = getChildStaticTransformation(child, mChildTransformation);
     83             if (hasTransform) {
     84                 final int transformType = mChildTransformation.getTransformationType();
     85                 transformToApply = transformType != Transformation.TYPE_IDENTITY ?
     86                         mChildTransformation : null;
     87                 concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
     88             }
     89         }
     90 
     91         // Sets the flag as early as possible to allow draw() implementations
     92         // to call invalidate() successfully when doing animations
     93         child.mPrivateFlags |= DRAWN;
     94 
     95         if (!concatMatrix && canvas.quickReject(cl, ct, cr, cb, Canvas.EdgeType.BW) &&
     96                 (child.mPrivateFlags & DRAW_ANIMATION) == 0) {
     97             return more;
     98         }
     99 
    100         child.computeScroll();
    101 
    102         final int sx = child.mScrollX;
    103         final int sy = child.mScrollY;
    104 
    105         boolean scalingRequired = false;
    106         Bitmap cache = null;
    107         if ((flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE ||
    108                 (flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) {
    109             cache = child.getDrawingCache(true);
    110             if (mAttachInfo != null) scalingRequired = mAttachInfo.mScalingRequired;
    111         }
    112 
    113         final boolean hasNoCache = cache == null;
    114 
    115         final int restoreTo = canvas.save();
    116         if (hasNoCache) {
    117             canvas.translate(cl - sx, ct - sy);
    118         } else {
    119             canvas.translate(cl, ct);
    120             if (scalingRequired) {
    121                 // mAttachInfo cannot be null, otherwise scalingRequired == false
    122                 final float scale = 1.0f / mAttachInfo.mApplicationScale;
    123                 canvas.scale(scale, scale);
    124             }
    125         }
    126 
    127         float alpha = 1.0f;
    128 
    129         if (transformToApply != null) {
    130             if (concatMatrix) {
    131                 int transX = 0;
    132                 int transY = 0;
    133                 if (hasNoCache) {
    134                     transX = -sx;
    135                     transY = -sy;
    136                 }
    137                 // Undo the scroll translation, apply the transformation matrix,
    138                 // then redo the scroll translate to get the correct result.
    139                 canvas.translate(-transX, -transY);
    140                 canvas.concat(transformToApply.getMatrix());
    141                 canvas.translate(transX, transY);
    142                 mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
    143             }
    144 
    145             alpha = transformToApply.getAlpha();
    146             if (alpha < 1.0f) {
    147                 mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
    148             }
    149 
    150             if (alpha < 1.0f && hasNoCache) {
    151                 final int multipliedAlpha = (int) (255 * alpha);
    152                 if (!child.onSetAlpha(multipliedAlpha)) {
    153                     canvas.saveLayerAlpha(sx, sy, sx + cr - cl, sy + cb - ct, multipliedAlpha,
    154                             Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
    155                 } else {
    156                     child.mPrivateFlags |= ALPHA_SET;
    157                 }
    158             }
    159         } else if ((child.mPrivateFlags & ALPHA_SET) == ALPHA_SET) {
    160             child.onSetAlpha(255);
    161         }
    162 
    163         if ((flags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
    164             if (hasNoCache) {
    165                 canvas.clipRect(sx, sy, sx + (cr - cl), sy + (cb - ct));
    166             } else {
    167                 if (!scalingRequired) {
    168                     canvas.clipRect(0, 0, cr - cl, cb - ct);
    169                 } else {
    170                     canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
    171                 }
    172             }
    173         }
    174 
    175         if (hasNoCache) {
    176             // Fast path for layouts with no backgrounds
    177             if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
    178                 if (ViewDebug.TRACE_HIERARCHY) {
    179                     ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
    180                 }
    181                 child.mPrivateFlags &= ~DIRTY_MASK;
    182                 child.dispatchDraw(canvas);
    183             } else {
    184                 child.draw(canvas);
    185             }
    186         } else {
    187             final Paint cachePaint = mCachePaint;
    188             if (alpha < 1.0f) {
    189                 cachePaint.setAlpha((int) (alpha * 255));
    190                 mGroupFlags |= FLAG_ALPHA_LOWER_THAN_ONE;
    191             } else if  ((flags & FLAG_ALPHA_LOWER_THAN_ONE) == FLAG_ALPHA_LOWER_THAN_ONE) {
    192                 cachePaint.setAlpha(255);
    193                 mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE;
    194             }
    195             if (Config.DEBUG && ViewDebug.profileDrawing) {
    196                 EventLog.writeEvent(60003, hashCode());
    197             }
    198             canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
    199         }
    200 
    201         canvas.restoreToCount(restoreTo);
    202 
    203         if (a != null && !more) {
    204             child.onSetAlpha(255);
    205             finishAnimatingView(child, a);
    206         }
    207 
    208         return more;
    209     }

    这个方法是ViewGroup用来绘制子View的,第100行!调用了子View的computeScroll()方法,从Android的View树来看,我们会知道,所有的View的computeScroll方法一定会被调用,而在View被刷新(也就是Draw()方法被调用的时候)也会调用这个方法(这里有待探究,我在源码里面追踪了很久没有找到很直接的方法调用路径)(当我们执行ontouch或invalidate()或postInvalidate()都会导致这个方法的执行所以我们像下面这样调用,postInvalidate执行后,会去调computeScroll 方法,而这个方法里再去调postInvalidate,这样就可以不断地去调用scrollTo方法了,直到mScroller动画结束,当然第一次时,我们需要手动去调用一次postInvalidate才会去调用 ),所以,假设我们存在一个方法可以调用Scroller的startScroll方法,然后立刻调用invalidate方法,这个时候就回去调用computeScroll方法,而在覆盖这个方法的时候,我们如下操作:

    1 @Override  
    2 public void computeScroll() {  
    3     if (mScroller.computeScrollOffset()) { // 如果返回true,表示动画还没有结束
    4         scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 
    5         postInvalidate();  
    6     } else { //如果返回false,表示startScroll完成  
    7         Log.i(tag, " scoller has finished -----");  
    8 }  

    这个方法一方面调用scrollTo方法,一方面,我们调用postInvalidate方法形成循环,直到MScroller.computeScrollOffset方法返回false,表示滚动结束的时候,我们停止刷新!另一个方法大致如下:

    1 public void scroll(){ 
    2    mScroller.startScroll(0, 0, menuItemWidth, 0,3000);  
    3    invalidate();  
    4 }  

    这样就可以通过View本身的滚动方法和Scroller配合,实现View的动画滚动。完整的例子: 

     1 import android.content.Context;  
     2 import android.util.AttributeSet;  
     3 import android.view.View;  
     4 import android.view.ViewGroup;  
     5 import android.widget.LinearLayout;  
     6 import android.widget.Scroller;  
     7   
     8 public class MyViewGroup extends LinearLayout {  
     9     private boolean s1=true;  
    10     Scroller mScroller=null;  
    11     public MyViewGroup(Context context, AttributeSet attrs) {  
    12         super(context, attrs);  
    13         mScroller=new Scroller(context);  
    14         // TODO Auto-generated constructor stub  
    15     }  
    16     @Override  
    17     public void computeScroll() {  
    18         if (mScroller.computeScrollOffset()) {  
    19             scrollTo(mScroller.getCurrX(), 0);  
    20             postInvalidate();  
    21         }  
    22     }  
    23     public void beginScroll(){  
    24         if (!s1) {  
    25             mScroller.startScroll(0, 0, 0, 0, 1000);  
    26             s1 = true;  
    27         } else {  
    28             mScroller.startScroll(0, 0, -500, 0, 1000);  
    29             s1 = false;  
    30         }  
    31         invalidate();  
    32     }  
    33 }  

    推荐博文:Android中滑屏实现----手把手教你如何实现触摸滑屏以及Scroller类详解 

  • 相关阅读:
    IMP-00010: 不是有效的导出文件,标题验证失败
    ORA-01261: Parameter db_recovery_file_dest destination string cannot be translat
    oracle启动,提示“LRM-00109: could not open parameter file”
    Linux下oracle数据库启动和关闭操作
    YARN学习总结之架构
    HDFS读写流程
    HDFS学习总结之API交互
    IO编程之NIO
    IO编程之对象序列化
    IO编程之IO流
  • 原文地址:https://www.cnblogs.com/lqminn/p/2936084.html
Copyright © 2020-2023  润新知