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