爲什麼要寫這篇博客?
以前在使用View的scrollBy()或者scrollTo()的時候,發現它們的參數在正的時候是反方向移動,負的時候是正方向移動。於是就google了下,發現好多博客都要麼是轉摘、要麼是直接抄襲然後美起名曰原創,更惡劣的是這些博文由於是轉摘抄襲的關係,竟然都說View在scrollBy()或者scrollTo()的時候,它們的直角座標系是相反的,這明顯是一個錯誤的觀念。
好了,廢話不多說進入正題。
在做分析之前,首先要建立起Android設備屏幕的平面直角座標系概念。在Android手機中,屏幕的直角座標軸概念簡單來說:
屏幕左上角爲直角座標系的原點(0,0)從原點出發向左爲X軸負方向,向右爲X軸正方向從原點出發向上爲Y軸負方向,向下爲Y軸正方向
上述概念可通過如下圖總結:
在Android中,我們通常說View在屏幕上的座標,其實就是view的左上的座標。調用View的invalidate()方法會促使View重繪。
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()在參數爲負的時候,向座標軸正方向滾動;當參數爲正的時候,向座標軸負方向滾動。而作爲我們的認知,應該是參數爲負的時候,向座標軸負方向滾動;當參數爲正的時候,向座標軸正方向滾動。
那爲什麼這兩個方法傳入參數和引起的滾動方向和我們平常的認知不同呢?
下面就讓我們帶着這個問題跟隨源碼分析。如果不想從它的執行過程一步步的去分析,可以直接看本文的最後一段源碼。
因爲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.
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.