当一个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 }