【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类详解 

原文地址:https://www.cnblogs.com/lqminn/p/2936084.html