转载----Android源码角度分析View的scrollBy()和scrollTo()的参数正负问题

 

作者:  发布日期:2014-04-21 20:37:58
 
  • 以前在使用View的scrollBy()或者scrollTo()的时候,发现它们的参数在正的时候是反方向移动,负的时候是正方向移动。于是就google了下,发现好多博客都要么是转摘、要么是直接抄袭然后美起名曰原创,更恶劣的是这些博文由于是转摘抄袭的关系,竟然都说View在scrollBy()或者scrollTo()的时候,它们的直角坐标系是相反的,这明显是一个错误的观念。

    好了,废话不多说进入正题。

    Android设备平面直角坐标系

    在做分析之前,首先要建立起Android设备屏幕的平面直角坐标系概念。在Android手机中,屏幕的直角坐标轴概念简单来说:

     

    屏幕左上角为直角坐标系的原点(0,0)从原点出发向左为X轴负方向,向右为X轴正方向从原点出发向上为Y轴负方向,向下为Y轴正方向

     

    上述概念可通过如下图总结:

     

    \
  • 在Android中,我们通常说View在屏幕上的坐标,其实就是view的左上的坐标。调用View的invalidate()方法会促使View重绘。

    View的scrollBy()和scrollTo()

    在分析scrollBy()和scrollTo()之前,先上一段源码片段:
    01. /**
    02.  * Set the scrolled position of your view. This will cause a call to
    03.  * {@link #onScrollChanged(int, int, int, int)} and the view will be
    04.  * invalidated.
    05.  * @param x the x position to scroll to
    06.  * @param y the y position to scroll to
    07.  */
    08. public void scrollTo(int x, int y) {
    09.     if (mScrollX != x || mScrollY != y) {
    10.         int oldX = mScrollX;
    11.         int oldY = mScrollY;
    12.         mScrollX = x;
    13.         mScrollY = y;
    14.         invalidateParentCaches();
    15.         onScrollChanged(mScrollX, mScrollY, oldX, oldY);
    16.         if (!awakenScrollBars()) {
    17.             invalidate(true);
    18.         }
    19.     }
    20. }
    21.  
    22. /**
    23.  * Move the scrolled position of your view. This will cause a call to
    24.  * {@link #onScrollChanged(int, int, int, int)} and the view will be
    25.  * invalidated.
    26.  * @param x the amount of pixels to scroll by horizontally
    27.  * @param y the amount of pixels to scroll by vertically
    28.  */
    29. public void scrollBy(int x, int y) {
    30.     scrollTo(mScrollX + x, mScrollY + y);
    31. }
    scrollBy()和scrollTo()的滚动不同点
    scrollTo(x, y):通过invalidate使view直接滚动到参数x和y所标定的坐标scrollBy(x, y):通过相对于当前坐标的滚动。从上面代码中,很容以就能看出scrollBy()的方法体只有调用scrollTo()方法的一行代码,scrollBy()方法先对属性mScollX加上参数x和属性mScrollY加上参数y,然后将上述结果作为参数传入调用方法scrollTo()
    scrollBy()和scrollTo()的参数正负影响滚动问题

    scrollBy()和scrollTo()在参数为负的时候,向坐标轴正方向滚动;当参数为正的时候,向坐标轴负方向滚动。而作为我们的认知,应该是参数为负的时候,向坐标轴负方向滚动;当参数为正的时候,向坐标轴正方向滚动。

    那为什么这两个方法传入参数和引起的滚动方向和我们平常的认知不同呢?

    下面就让我们带着这个问题跟随源码分析。如果不想从它的执行过程一步步的去分析,可以直接看本文的最后一段源码。

    源码执行过程分析

    因为scrollBy(x, y)方法体只有一行,并且是调用scrollTo(x, y),所以我们只要通过scrollTo(x, y)来进行分析就可以了。

    在scrollTo(x, y)中,x和y分别被赋值给了mScrollX和mScrollY,最后调用了方法invalidate(true)。貌似到了这里就无路可走了,其实不然,我们知道invalidate这个方法会通知View进行重绘。

    那么接下来,我们就可以跳过scrollTo(x, y)去分析View的draw()方法了。照例,在分析onDraw方法之前上一段源码片段:

     

    001. /**
    002.  * Manually render this view (and all of its children) to the given Canvas.
    003.  * The view must have already done a full layout before this function is
    004.  * called.  When implementing a view, implement
    005.  * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
    006.  * If you do need to override this method, call the superclass version.
    007.  *
    008.  * @param canvas The Canvas to which the View is rendered.
    009.  */
    010. public void draw(Canvas canvas) {
    011.     if (ViewDebug.TRACE_HIERARCHY) {
    012.         ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
    013.     }
    014.  
    015.     final int privateFlags = mPrivateFlags;
    016.     final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
    017.             (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    018.     mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
    019.  
    020.     /*
    021.      * Draw traversal performs several drawing steps which must be executed
    022.      * in the appropriate order:
    023.      *
    024.      *      1. Draw the background
    025.      *      2. If necessary, save the canvas' layers to prepare for fading
    026.      *      3. Draw view's content
    027.      *      4. Draw children
    028.      *      5. If necessary, draw the fading edges and restore layers
    029.      *      6. Draw decorations (scrollbars for instance)
    030.      */
    031.  
    032.     // Step 1, draw the background, if needed
    033.     int saveCount;
    034.  
    035.     if (!dirtyOpaque) {
    036.         final Drawable background = mBGDrawable;
    037.         if (background != null) {
    038.             final int scrollX = mScrollX;
    039.             final int scrollY = mScrollY;
    040.  
    041.             if (mBackgroundSizeChanged) {
    042.                 background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
    043.                 mBackgroundSizeChanged = false;
    044.             }
    045.  
    046.             if ((scrollX | scrollY) == 0) {
    047.                 background.draw(canvas);
    048.             } else {
    049.                 canvas.translate(scrollX, scrollY);
    050.                 background.draw(canvas);
    051.                 canvas.translate(-scrollX, -scrollY);
    052.             }
    053.         }
    054.     }
    055.  
    056.     // skip step 2 & 5 if possible (common case)
    057.     final int viewFlags = mViewFlags;
    058.     boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    059.     boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    060.     if (!verticalEdges && !horizontalEdges) {
    061.         // Step 3, draw the content
    062.         if (!dirtyOpaque) onDraw(canvas);
    063.  
    064.         // Step 4, draw the children
    065.         dispatchDraw(canvas);
    066.  
    067.         // Step 6, draw decorations (scrollbars)
    068.         onDrawScrollBars(canvas);
    069.  
    070.         // we're done...
    071.         return;
    072.     }
    073.  
    074.     /*
    075.      * Here we do the full fledged routine...
    076.      * (this is an uncommon case where speed matters less,
    077.      * this is why we repeat some of the tests that have been
    078.      * done above)
    079.      */
    080.  
    081.     boolean drawTop = false;
    082.     boolean drawBottom = false;
    083.     boolean drawLeft = false;
    084.     boolean drawRight = false;
    085.  
    086.     float topFadeStrength = 0.0f;
    087.     float bottomFadeStrength = 0.0f;
    088.     float leftFadeStrength = 0.0f;
    089.     float rightFadeStrength = 0.0f;
    090.  
    091.     // Step 2, save the canvas' layers
    092.     int paddingLeft = mPaddingLeft;
    093.  
    094.     final boolean offsetRequired = isPaddingOffsetRequired();
    095.     if (offsetRequired) {
    096.         paddingLeft += getLeftPaddingOffset();
    097.     }
    098.  
    099.     int left = mScrollX + paddingLeft;
    100.     int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
    101.     int top = mScrollY + getFadeTop(offsetRequired);
    102.     int bottom = top + getFadeHeight(offsetRequired);
    103.  
    104.     if (offsetRequired) {
    105.         right += getRightPaddingOffset();
    106.         bottom += getBottomPaddingOffset();
    107.     }
    108.  
    109.     final ScrollabilityCache scrollabilityCache = mScrollCache;
    110.     final float fadeHeight = scrollabilityCache.fadingEdgeLength;       
    111.     int length = (int) fadeHeight;
    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 * fadeHeight > 1.0f;
    127.         bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
    128.         drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
    129.     }
    130.  
    131.     if (horizontalEdges) {
    132.         leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
    133.         drawLeft = leftFadeStrength * fadeHeight > 1.0f;
    134.         rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
    135.         drawRight = rightFadeStrength * fadeHeight > 1.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.  
    174.     if (drawTop) {
    175.         matrix.setScale(1, fadeHeight * topFadeStrength);
    176.         matrix.postTranslate(left, top);
    177.         fade.setLocalMatrix(matrix);
    178.         canvas.drawRect(left, top, right, top + length, p);
    179.     }
    180.  
    181.     if (drawBottom) {
    182.         matrix.setScale(1, fadeHeight * bottomFadeStrength);
    183.         matrix.postRotate(180);
    184.         matrix.postTranslate(left, bottom);
    185.         fade.setLocalMatrix(matrix);
    186.         canvas.drawRect(left, bottom - length, right, bottom, p);
    187.     }
    188.  
    189.     if (drawLeft) {
    190.         matrix.setScale(1, fadeHeight * leftFadeStrength);
    191.         matrix.postRotate(-90);
    192.         matrix.postTranslate(left, top);
    193.         fade.setLocalMatrix(matrix);
    194.         canvas.drawRect(left, top, left + length, bottom, p);
    195.     }
    196.  
    197.     if (drawRight) {
    198.         matrix.setScale(1, fadeHeight * rightFadeStrength);
    199.         matrix.postRotate(90);
    200.         matrix.postTranslate(right, top);
    201.         fade.setLocalMatrix(matrix);
    202.         canvas.drawRect(right - length, top, right, bottom, p);
    203.     }
    204.  
    205.     canvas.restoreToCount(saveCount);
    206.  
    207.     // Step 6, draw decorations (scrollbars)
    208.     onDrawScrollBars(canvas);
    209. }

    在这段代码片中,我们直接定位到onDrawScrollBars(canvas)方法,找到了这个方法离真相就不远了。上源码:

    001. /**
    002.  * <p>Request the drawing of the horizontal and the vertical scrollbar. The
    003.  * scrollbars are painted only if they have been awakened first.</p>
    004.  *
    005.  * @param canvas the canvas on which to draw the scrollbars
    006.  *
    007.  * @see #awakenScrollBars(int)
    008.  */
    009. protected final void onDrawScrollBars(Canvas canvas) {
    010.     // scrollbars are drawn only when the animation is running
    011.     final ScrollabilityCache cache = mScrollCache;
    012.     if (cache != null) {
    013.  
    014.         int state = cache.state;
    015.  
    016.         if (state == ScrollabilityCache.OFF) {
    017.             return;
    018.         }
    019.  
    020.         boolean invalidate = false;
    021.  
    022.         if (state == ScrollabilityCache.FADING) {
    023.             // We're fading -- get our fade interpolation
    024.             if (cache.interpolatorValues == null) {
    025.                 cache.interpolatorValues = new float[1];
    026.             }
    027.  
    028.             float[] values = cache.interpolatorValues;
    029.  
    030.             // Stops the animation if we're done
    031.             if (cache.scrollBarInterpolator.timeToValues(values) ==
    032.                     Interpolator.Result.FREEZE_END) {
    033.                 cache.state = ScrollabilityCache.OFF;
    034.             } else {
    035.                 cache.scrollBar.setAlpha(Math.round(values[0]));
    036.             }
    037.  
    038.             // This will make the scroll bars inval themselves after
    039.             // drawing. We only want this when we're fading so that
    040.             // we prevent excessive redraws
    041.             invalidate = true;
    042.         } else {
    043.             // We're just on -- but we may have been fading before so
    044.             // reset alpha
    045.             cache.scrollBar.setAlpha(255);
    046.         }
    047.  
    048.  
    049.         final int viewFlags = mViewFlags;
    050.  
    051.         final boolean drawHorizontalScrollBar =
    052.             (viewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL;
    053.         final boolean drawVerticalScrollBar =
    054.             (viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL
    055.             && !isVerticalScrollBarHidden();
    056.  
    057.         if (drawVerticalScrollBar || drawHorizontalScrollBar) {
    058.             final int width = mRight - mLeft;
    059.             final int height = mBottom - mTop;
    060.  
    061.             final ScrollBarDrawable scrollBar = cache.scrollBar;
    062.  
    063.             final int scrollX = mScrollX;
    064.             final int scrollY = mScrollY;
    065.             final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
    066.  
    067.             int left, top, right, bottom;
    068.  
    069.             if (drawHorizontalScrollBar) {
    070.                 int size = scrollBar.getSize(false);
    071.                 if (size <= 0) {
    072.                     size = cache.scrollBarSize;
    073.                 }
    074.  
    075.                 scrollBar.setParameters(computeHorizontalScrollRange(),
    076.                                         computeHorizontalScrollOffset(),
    077.                                         computeHorizontalScrollExtent(), false);
    078.                 final int verticalScrollBarGap = drawVerticalScrollBar ?
    079.                         getVerticalScrollbarWidth() : 0;
    080.                 top = scrollY + height - size - (mUserPaddingBottom & inside);
    081.                 left = scrollX + (mPaddingLeft & inside);
    082.                 right = scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap;
    083.                 bottom = top + size;
    084.                 onDrawHorizontalScrollBar(canvas, scrollBar, left, top, right, bottom);
    085.                 if (invalidate) {
    086.                     invalidate(left, top, right, bottom);
    087.                 }
    088.             }
    089.  
    090.             if (drawVerticalScrollBar) {
    091.                 int size = scrollBar.getSize(true);
    092.                 if (size <= 0) {
    093.                     size = cache.scrollBarSize;
    094.                 }
    095.  
    096.                 scrollBar.setParameters(computeVerticalScrollRange(),
    097.                                         computeVerticalScrollOffset(),
    098.                                         computeVerticalScrollExtent(), true);
    099.                 switch (mVerticalScrollbarPosition) {
    100.                     default:
    101.                     case SCROLLBAR_POSITION_DEFAULT:
    102.                     case SCROLLBAR_POSITION_RIGHT:
    103.                         left = scrollX + width - size - (mUserPaddingRight & inside);
    104.                         break;
    105.                     case SCROLLBAR_POSITION_LEFT:
    106.                         left = scrollX + (mUserPaddingLeft & inside);
    107.                         break;
    108.                 }
    109.                 top = scrollY + (mPaddingTop & inside);
    110.                 right = left + size;
    111.                 bottom = scrollY + height - (mUserPaddingBottom & inside);
    112.                 onDrawVerticalScrollBar(canvas, scrollBar, left, top, right, bottom);
    113.                 if (invalidate) {
    114.                     invalidate(left, top, right, bottom);
    115.                 }
    116.             }
    117.         }
    118.     }
    119. }

    上述代码,我们直接定位到if (drawVerticalScrollBar || drawHorizontalScrollBar)结构语句块。在水平方向滚动与垂直方向滚动语句块中,能够找到一行关键性代码invalidate(left, top, right, bottom),接着上源码:

    01. /**
    02.  * Mark the area defined by the rect (l,t,r,b) as needing to be drawn.
    03.  * The coordinates of the dirty rect are relative to the view.
    04.  * If the view is visible, {@link #onDraw(android.graphics.Canvas)}
    05.  * will be called at some point in the future. This must be called from
    06.  * a UI thread. To call from a non-UI thread, call {@link #postInvalidate()}.
    07.  * @param l the left position of the dirty region
    08.  * @param t the top position of the dirty region
    09.  * @param r the right position of the dirty region
    10.  * @param b the bottom position of the dirty region
    11.  */
    12. public void invalidate(int l, int t, int r, int b) {
    13.     if (ViewDebug.TRACE_HIERARCHY) {
    14.         ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
    15.     }
    16.  
    17.     if (skipInvalidate()) {
    18.         return;
    19.     }
    20.     if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
    21.             (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID ||
    22.             (mPrivateFlags & INVALIDATED) != INVALIDATED) {
    23.         mPrivateFlags &= ~DRAWING_CACHE_VALID;
    24.         mPrivateFlags |= INVALIDATED;
    25.         mPrivateFlags |= DIRTY;
    26.         final ViewParent p = mParent;
    27.         final AttachInfo ai = mAttachInfo;
    28.         //noinspection PointlessBooleanExpression,ConstantConditions
    29.         if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
    30.             if (p != null && ai != null && ai.mHardwareAccelerated) {
    31.                 // fast-track for GL-enabled applications; just invalidate the whole hierarchy
    32.                 // with a null dirty rect, which tells the ViewAncestor to redraw everything
    33.                 p.invalidateChild(this, null);
    34.                 return;
    35.             }
    36.         }
    37.         if (p != null && ai != null && l < r && t < b) {
    38.             final int scrollX = mScrollX;
    39.             final int scrollY = mScrollY;
    40.             final Rect tmpr = ai.mTmpInvalRect;
    41.             tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);
    42.             p.invalidateChild(this, tmpr);
    43.         }
    44.     }
    45. }

    invalidate(left, top, right, bottom)方法体中,倒数第5行tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY)设置一个view需要绘制的脏矩形,这个方法的传入参数不觉得很奇怪吗?
    mScrollX和mScrollY都是作为参数的减数(负负得正,负正得负),再结合开头的Android屏幕直角坐标系的概念,通过简单的逻辑分析或者计算就可以证明:当scrollTo()的传入参数为负的时候,view就向坐标轴正方向滚动;当为正的时候,view就向坐标轴负方向滚动。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值