前言
因为最近开始试着把一些阅读到的东西转化吸收一下,并且也训练一下书写的技能,所以上来纪录一下。
另外也看许多人说 source code 有机会有时间还是要加减看看,所以想说藉这个机会有顺路看到的就整理整理上来,当做自己的纪录也让有需要的人了解或是讨论,要是我有哪边写错或是不完整的也欢迎补充交流喔~~
在实际应用和说明前先简单看一下 scrollTo 以及 scrollBy 的原始码。
/*** The offset, in pixels, by which the content of this view is scrolled* horizontally.* Please use {@link View#getScrollX()} and {@link View#setScrollX(int)} instead of* accessing these directly.* {@hide}*/@ViewDebug.ExportedProperty(category = "scrolling")@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)protected int mScrollX;/*** The offset, in pixels, by which the content of this view is scrolled* vertically.* Please use {@link View#getScrollY()} and {@link View#setScrollY(int)} instead of* accessing these directly.* {@hide}*/@ViewDebug.ExportedProperty(category = "scrolling")@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)protected int mScrollY;/*** Return the scrolled left position of this view. This is the left edge of* the displayed part of your view. You do not need to draw any pixels* farther left, since those are outside of the frame of your view on* screen.** @return The left edge of the displayed part of your view, in pixels.*/@InspectablePropertypublic final int getScrollX() {return mScrollX;}/*** Return the scrolled top position of this view. This is the top edge of* the displayed part of your view. You do not need to draw any pixels above* it, since those are outside of the frame of your view on screen.** @return The top edge of the displayed part of your view, in pixels.*/@InspectablePropertypublic final int getScrollY() {return mScrollY;}
在这边我们可以注意到 mScrollX 和 mScrollY 两个都是偏移量,而非实际座标(相对于原点(0, 0)偏移的量),并且取用两者的时候应该要使用 getter 和 setter 使用。
再来是 scrollTo 还有 scrollBy 的部分,
/** * Set the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the x position to scroll to * @param y the y position to scroll to */ public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { postInvalidateOnAnimation(); } } } /** * Move the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the amount of pixels to scroll by horizontally * @param y the amount of pixels to scroll by vertically */ public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
从他们两者的原始码我们可以看出来其实两者是差不多的,差别是:
scrollTo:如果"偏移量"发生改变,就会改变 mScrollX 和 mScrollY 赋予它们新的值,以改变当前位置。简单来说就是将一个 view 从旧的位置移到新的位置。scrollBy:我们可以从他传入 srcollTo 的部分发现,他是基于原本的偏移基础上再发生偏移。换句话说就是我们想要把这个 view 移动多远,那我们就将这个移动距离填入即可。大致知道 scrollTo 以及 scrollBy 的关係后,我们再延伸阅读一下,如果单看右图左边的部分在 Android 手机上坐标系的关係图,如果我们今天打算使用 scroll(0, 20) 会发生甚么是情呢,向下移动20单位吗?不!事实上他会向上移动20单位。这是为甚么呢?
那这个部分就需要我们稍微看一下原始码的部分才能了解了,在前面的 scrollTo() 的尾巴我们可以看到他最后呼叫了postInvalidateOnAnimation(),那这代表着未来这个方法会去通知 View 进行重绘。所以我们来看一下 draw() 的原始码,那这个部分就只附上比较重要的部分:
/*** Manually render this view (and all of its children) to the given Canvas.* The view must have already done a full layout before this function is* called. When implementing a view, implement* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.* If you do need to override this method, call the superclass version.** @param canvas The Canvas to which the View is rendered.*/@CallSuperpublic void draw(Canvas canvas) {final int privateFlags = mPrivateFlags;mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;/** Draw traversal performs several drawing steps which must be executed* in the appropriate order:** 1. Draw the background* 2. If necessary, save the canvas' layers to prepare for fading* 3. Draw view's content* 4. Draw children* 5. If necessary, draw the fading edges and restore layers* 6. Draw decorations (scrollbars for instance)*/...// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);...}
首先我们可以从中间的注解看到绘製滚动条的部分 (scrollbars for instance) 是在第六步进行的 ,因此我们注意一下 Step 6的地方,他去呼叫了onDrawForeground,那我们再跟过去会发现在这个方法里面他会去呼叫 onDrawScrollBars(canvas),这个函示会分别去绘製水平方向以及垂直方向的 ScrollBar。
/*** Draw any foreground content for this view.** <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground}* drawable or other view-specific decorations. The foreground is drawn on top of the* primary view content.</p>** @param canvas canvas to draw into*/public void onDrawForeground(Canvas canvas) {onDrawScrollIndicators(canvas);onDrawScrollBars(canvas);final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;if (foreground != null) {...}}
而在 onDrawScrollBars(canvas) 中我们简单的观察一下会发现,在这个 function 中最后都会去调用一个函式 invalidate(bounds)
/*** <p>Request the drawing of the horizontal and the vertical scrollbar. The* scrollbars are painted only if they have been awakened first.</p>** @param canvas the canvas on which to draw the scrollbars** @see #awakenScrollBars(int)*/ protected final void onDrawScrollBars(Canvas canvas) { // scrollbars are drawn only when the animation is running final ScrollabilityCache cache = mScrollCache; if (cache != null) {... final boolean drawHorizontalScrollBar = isHorizontalScrollBarEnabled(); final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden(); // Fork out the scroll bar drawing for round wearable devices. if (mRoundScrollbarRenderer != null) { if (drawVerticalScrollBar) { ... if (invalidate) { invalidate(); } } // Do not draw horizontal scroll bars for round wearable devices. } else if (drawVerticalScrollBar || drawHorizontalScrollBar) { ... if (drawHorizontalScrollBar) { ... if (invalidate) { invalidate(bounds); } } if (drawVerticalScrollBar) { ... if (invalidate) { invalidate(bounds); } } } }}
这边因为这个function比较长,所以我删减了部分内容留下我们现在比较需要关注的点。到这边相信也有感觉到离我们想知道的事情很近了,在这边顺便补充一下,我们看到 invalidate 中传入的 bound 的型别式 Rect,也就是常用来绘製矩形之类的型态。下面附上他的属性。
Rect(int left, int top, int right, int bottom)
看到传入了上下左右的数值有没有突然觉得下一步就是我们想知道的地方啊,最后我们来看一下 invalidate(bounds) 中的一个部分。
/*** Mark the area defined by dirty as needing to be drawn. If the view is* visible, {@link #onDraw(android.graphics.Canvas)} will be called at some* point in the future.* <p>* This must be called from a UI thread. To call from a non-UI thread, call* {@link #postInvalidate()}.* <p>* <b>WARNING:</b> In API 19 and below, this method may be destructive to* {@code dirty}.** @param dirty the rectangle representing the bounds of the dirty region** @deprecated The switch to hardware accelerated rendering in API 14 reduced* the importance of the dirty rectangle. In API 21 the given rectangle is* ignored entirely in favor of an internally-calculated area instead.* Because of this, clients are encouraged to just call {@link #invalidate()}.*/@Deprecatedpublic void invalidate(Rect dirty) {final int scrollX = mScrollX;final int scrollY = mScrollY;invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,dirty.right - scrollX, dirty.bottom - scrollY, true, false);}
看到这个原始码有没有一点点小悸动阿,是不是发现了甚么!那就是在他的尾巴有一个 dirty.left - scrollX, dirty.top - scrollY, dirty.right - scrollX, dirty.bottom - scrollY
,相信你看到这边应该也知道为甚么我们输入的数值会跟我们一开始预期的相反了吧~