Android源碼角度分析View的scrollBy()和scrollTo()的參數正負問題

爲什麼要寫這篇博客?

以前在使用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(00,  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. 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. *
* scrollbars are painted only if they have been awakened first.</p>
004.
相關文章
相關標籤/搜索