【Android 疑难杂症】scrollTo和scrollBy探究

首先,需要知道的是,View是可以延伸到屏幕之外的,可以想象一下ListVIew或GridView。也就是说View的尺寸可以超过屏幕的尺寸。View的大小就是onDraw()中Canvas画布的大小。Canvas可以做translate()、clipRec()t等变换,可以说Canvas是无边界的。而我们在屏幕上所见到的,只是Canvas的一部分而已。可以调用View的scrollTo()和scrollBy()将视图绘制到指定区域。那么View中的scrollTo()和scrollBy()又是怎么回事呢?

要想一探究竟,就需要研究一下View的源码。

在View的源码中,mScrollX和mScrollY是视图在X轴和Y轴的偏移量。源码注释说的非常清楚xScrollX和xScrollY代表的是什么。

[java]  view plain copy
  1. /** 
  2.  * The offset, in pixels, by which the content of this view is scrolled 
  3.  * horizontally. 
  4.  * {@hide} 
  5.  */  
  6. @ViewDebug.ExportedProperty(category = "scrolling")  
  7. protected int mScrollX;  
  8. /** 
  9.  * The offset, in pixels, by which the content of this view is scrolled 
  10.  * vertically. 
  11.  * {@hide} 
  12.  */  
  13. @ViewDebug.ExportedProperty(category = "scrolling")  
  14. protected int mScrollY;  
  15.   
  16.   
  17.  /** 
  18.  * Return the scrolled left position of this view. This is the left edge of 
  19.  * the displayed part of your view. You do not need to draw any pixels 
  20.  * farther left, since those are outside of the frame of your view on 
  21.  * screen. 
  22.  * 
  23.  * @return The left edge of the displayed part of your view, in pixels. 
  24.  */  
  25. public final int getScrollX() {  
  26.     return mScrollX;  
  27. }  
  28.   
  29. /** 
  30.  * Return the scrolled top position of this view. This is the top edge of 
  31.  * the displayed part of your view. You do not need to draw any pixels above 
  32.  * it, since those are outside of the frame of your view on screen. 
  33.  * 
  34.  * @return The top edge of the displayed part of your view, in pixels. 
  35.  */  
  36. public final int getScrollY() {  
  37.     return mScrollY;  
  38. }  

知道了mScrollX和mScrollY的含义,接下来再看scrollTo()和scrollBy()的具体实现,代码如下:

[java]  view plain copy
  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. }  

从源码中可以看到,scrollBy()的内部其实是调用了scrollTo()。在scrollTo()中,调用了onScrollChanged()和invalidate()。

onScrollChanged()的作用就是告诉系统(可以理解为Android框架),这个View的scrollTo()或scrollBy()曾经被调用过;而invalidate()是告诉系统,这个View需要被重新绘制。

接下来,探究一下onScrollChanged()和invalidate()的具体实现,代码如下:

[java]  view plain copy
  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. }  
[java]  view plain copy
  1. /** 
  2.      * Invalidate the whole view. If the view is visible, {@link #onDraw} will 
  3.      * be called at some point in the future. This must be called from a 
  4.      * UI thread. To call from a non-UI thread, call {@link #postInvalidate()}. 
  5.      */  
  6.     public void invalidate() {  
  7.         if (ViewDebug.TRACE_HIERARCHY) {  
  8.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);  
  9.         }  
  10.   
  11.         if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {  
  12.             mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;  
  13.             final ViewParent p = mParent;  
  14.             final AttachInfo ai = mAttachInfo;  
  15.             if (p != null && ai != null) {  
  16.                 final Rect r = ai.mTmpInvalRect;  
  17.                 r.set(00, mRight - mLeft, mBottom - mTop);  
  18.                 // Don't call invalidate -- we don't want to internally scroll  
  19.                 // our own bounds  
  20.                 p.invalidateChild(this, r);  
  21.             }  
  22.         }  
  23.     }  

知道了scrollTo()和scrollBy()的意义,那么举个例子,感性地认识一下。

假设有一个View,它叫做SView。

如果想把SView从(0, 0)移动到(100, 100)。注意,这里说的(0, 0)和(100, 100),指的是SView左上角的坐标。那么偏移量就是原点(0, 0)到目标点(100, 100)的距离,即(0 , 0) - (100, 100) = (-100, -100)。

只需要调用SView.scrollTo(-100, -100)就可以了。请再次注意,scrollTo(int x, int y)的两个参数x和y,代表的是偏移量,这时的参照物是(0, 0)点。

然而,scrollBy()是有一定的区别的。scrollBy()的参照物是(0, 0)点加上偏移量之后的坐标。

这么描述比较抽象,举个例子。假设SView调用了scrollTo(-100, -100),此时SView左上角的坐标是(100, 100),这时再调用scrollBy(-20, -20),此时SView的左上角就被绘制到了(120, 120)这个位置。

总结一下,scrollTo()是一步到位,而scrollBy()是逐步累加。


那么mScrollX和mScrollY又是在哪里被使用的呢?

上面说过,scrollTo()会使视图重绘,那究竟是如何绘制的?请看draw()方法,代码如下:

[java]  view plain copy
  1. /** 
  2.  * Manually render this view (and all of its children) to the given Canvas. 
  3.  * The view must have already done a full layout before this function is 
  4.  * called.  When implementing a view, do not override this method; instead, 
  5.  * you should implement {@link #onDraw}. 
  6.  * 
  7.  * @param canvas The Canvas to which the View is rendered. 
  8.  */  
  9. public void draw(Canvas canvas) {  
  10.     if (ViewDebug.TRACE_HIERARCHY) {  
  11.         ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);  
  12.     }  
  13.   
  14.     final int privateFlags = mPrivateFlags;  
  15.     final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&  
  16.             (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);  
  17.     mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;  
  18.   
  19.     /* 
  20.      * Draw traversal performs several drawing steps which must be executed 
  21.      * in the appropriate order: 
  22.      * 
  23.      *      1. Draw the background 
  24.      *      2. If necessary, save the canvas' layers to prepare for fading 
  25.      *      3. Draw view's content 
  26.      *      4. Draw children 
  27.      *      5. If necessary, draw the fading edges and restore layers 
  28.      *      6. Draw decorations (scrollbars for instance) 
  29.      */  
  30.   
  31.     // Step 1, draw the background, if needed  
  32.     int saveCount;  
  33.   
  34.     if (!dirtyOpaque) {  
  35.         final Drawable background = mBGDrawable;  
  36.         if (background != null) {  
  37.             final int scrollX = mScrollX;  
  38.             final int scrollY = mScrollY;  
  39.   
  40.             if (mBackgroundSizeChanged) {  
  41.                 background.setBounds(00,  mRight - mLeft, mBottom - mTop);  
  42.                 mBackgroundSizeChanged = false;  
  43.             }  
  44.   
  45.             if ((scrollX | scrollY) == 0) {  
  46.                 background.draw(canvas);  
  47.             } else {  
  48.                 canvas.translate(scrollX, scrollY);  
  49.                 background.draw(canvas);  
  50.                 canvas.translate(-scrollX, -scrollY);  
  51.             }  
  52.         }  
  53.     }  
  54.   
  55.     // skip step 2 & 5 if possible (common case)  
  56.     final int viewFlags = mViewFlags;  
  57.     boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;  
  58.     boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;  
  59.     if (!verticalEdges && !horizontalEdges) {  
  60.         // Step 3, draw the content  
  61.         if (!dirtyOpaque) onDraw(canvas);  
  62.   
  63.         // Step 4, draw the children  
  64.         dispatchDraw(canvas);  
  65.   
  66.         // Step 6, draw decorations (scrollbars)  
  67.         onDrawScrollBars(canvas);  
  68.   
  69.         // we're done...  
  70.         return;  
  71.     }  
  72.   
  73.     /* 
  74.      * Here we do the full fledged routine... 
  75.      * (this is an uncommon case where speed matters less, 
  76.      * this is why we repeat some of the tests that have been 
  77.      * done above) 
  78.      */  
  79.   
  80.     boolean drawTop = false;  
  81.     boolean drawBottom = false;  
  82.     boolean drawLeft = false;  
  83.     boolean drawRight = false;  
  84.   
  85.     float topFadeStrength = 0.0f;  
  86.     float bottomFadeStrength = 0.0f;  
  87.     float leftFadeStrength = 0.0f;  
  88.     float rightFadeStrength = 0.0f;  
  89.   
  90.     // Step 2, save the canvas' layers  
  91.     int paddingLeft = mPaddingLeft;  
  92.     int paddingTop = mPaddingTop;  
  93.   
  94.     final boolean offsetRequired = isPaddingOffsetRequired();  
  95.     if (offsetRequired) {  
  96.         paddingLeft += getLeftPaddingOffset();  
  97.         paddingTop += getTopPaddingOffset();  
  98.     }  
  99.   
  100.     int left = mScrollX + paddingLeft;  
  101.     int right = left + mRight - mLeft - mPaddingRight - paddingLeft;  
  102.     int top = mScrollY + paddingTop;  
  103.     int bottom = top + mBottom - mTop - mPaddingBottom - paddingTop;  
  104.   
  105.     if (offsetRequired) {  
  106.         right += getRightPaddingOffset();  
  107.         bottom += getBottomPaddingOffset();  
  108.     }  
  109.   
  110.     final ScrollabilityCache scrollabilityCache = mScrollCache;  
  111.     int length = scrollabilityCache.fadingEdgeLength;  
  112.   
  113.     // clip the fade length if top and bottom fades overlap  
  114.     // overlapping fades produce odd-looking artifacts  
  115.     if (verticalEdges && (top + length > bottom - length)) {  
  116.         length = (bottom - top) / 2;  
  117.     }  
  118.   
  119.     // also clip horizontal fades if necessary  
  120.     if (horizontalEdges && (left + length > right - length)) {  
  121.         length = (right - left) / 2;  
  122.     }  
  123.   
  124.     if (verticalEdges) {  
  125.         topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));  
  126.         drawTop = topFadeStrength >= 0.0f;  
  127.         bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));  
  128.         drawBottom = bottomFadeStrength >= 0.0f;  
  129.     }  
  130.   
  131.     if (horizontalEdges) {  
  132.         leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));  
  133.         drawLeft = leftFadeStrength >= 0.0f;  
  134.         rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));  
  135.         drawRight = rightFadeStrength >= 0.0f;  
  136.     }  
  137.   
  138.     saveCount = canvas.getSaveCount();  
  139.   
  140.     int solidColor = getSolidColor();  
  141.     if (solidColor == 0) {  
  142.         final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;  
  143.   
  144.         if (drawTop) {  
  145.             canvas.saveLayer(left, top, right, top + length, null, flags);  
  146.         }  
  147.   
  148.         if (drawBottom) {  
  149.             canvas.saveLayer(left, bottom - length, right, bottom, null, flags);  
  150.         }  
  151.   
  152.         if (drawLeft) {  
  153.             canvas.saveLayer(left, top, left + length, bottom, null, flags);  
  154.         }  
  155.   
  156.         if (drawRight) {  
  157.             canvas.saveLayer(right - length, top, right, bottom, null, flags);  
  158.         }  
  159.     } else {  
  160.         scrollabilityCache.setFadeColor(solidColor);  
  161.     }  
  162.   
  163.     // Step 3, draw the content  
  164.     if (!dirtyOpaque) onDraw(canvas);  
  165.   
  166.     // Step 4, draw the children  
  167.     dispatchDraw(canvas);  
  168.   
  169.     // Step 5, draw the fade effect and restore layers  
  170.     final Paint p = scrollabilityCache.paint;  
  171.     final Matrix matrix = scrollabilityCache.matrix;  
  172.     final Shader fade = scrollabilityCache.shader;  
  173.     final float fadeHeight = scrollabilityCache.fadingEdgeLength;  
  174.   
  175.     if (drawTop) {  
  176.         matrix.setScale(1, fadeHeight * topFadeStrength);  
  177.         matrix.postTranslate(left, top);  
  178.         fade.setLocalMatrix(matrix);  
  179.         canvas.drawRect(left, top, right, top + length, p);  
  180.     }  
  181.   
  182.     if (drawBottom) {  
  183.         matrix.setScale(1, fadeHeight * bottomFadeStrength);  
  184.         matrix.postRotate(180);  
  185.         matrix.postTranslate(left, bottom);  
  186.         fade.setLocalMatrix(matrix);  
  187.         canvas.drawRect(left, bottom - length, right, bottom, p);  
  188.     }  
  189.   
  190.     if (drawLeft) {  
  191.         matrix.setScale(1, fadeHeight * leftFadeStrength);  
  192.         matrix.postRotate(-90);  
  193.         matrix.postTranslate(left, top);  
  194.         fade.setLocalMatrix(matrix);  
  195.         canvas.drawRect(left, top, left + length, bottom, p);  
  196.     }  
  197.   
  198.     if (drawRight) {  
  199.         matrix.setScale(1, fadeHeight * rightFadeStrength);  
  200.         matrix.postRotate(90);  
  201.         matrix.postTranslate(right, top);  
  202.         fade.setLocalMatrix(matrix);  
  203.         canvas.drawRect(right - length, top, right, bottom, p);  
  204.     }  
  205.   
  206.     canvas.restoreToCount(saveCount);  
  207.   
  208.     // Step 6, draw decorations (scrollbars)  
  209.     onDrawScrollBars(canvas);  
  210. }  
  211. 参考资料

    http://blog.csdn.net/qinjuning/article/details/7247126

    http://blog.csdn.net/vipzjyno1/article/details/24577023

    http://developer.android.com/reference/android/view/View.html#scrollTo%28int,%20int%29


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值