在前邊的文章中,咱們已經對Android觸摸事件處理有了大體的瞭解,而且詳細探討了MotionEvent
的相關用法。對以前文章中的知識還不是很瞭解的同窗,請閱讀《Android MotionEvent詳解》。html
今天,咱們就來探討一下Android中界面滾動效果的相關機制,本篇文章主要講解一下滾動相關的知識點,以後的文章會涉及實際的代碼和原理。但願你們閱讀完這篇文章以後,可以瞭解或者掌握一下知識:java
Android 視圖的組成部分android
mScrollX
和mScrollY
對視圖顯示的影響git
scrollTo
和scrollBy
的使用github
invalidate
和postInvalidate
的區別canvas
咱們都知道,View
中有兩個重要的成員變量,mScrollX
,mScrollY
.它們分別表明視圖內容(view content)水平方向和豎直方向的滾動距離。咱們能夠經過setScrollX
和setScrollY
來個函數來改變它們的值,從而來滾動視圖的內容。app
在這裏須要強調的是,mScrollX
和mScrollY
會致使視圖內容(view content)變化,可是不會影響視圖背景(background)。函數
看到這裏同窗們或許會有寫疑問,視圖的內容和背景有什麼區別呢?視圖還有哪些組成部分呢?佈局
咱們能夠從View的draw
方法中得知View的組成部分。post
// http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/view/View.java#View public void draw(Canvas canvas) { ........ /* * 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 1, draw the background, if needed if (!dirtyOpaque) { drawBackground(canvas); } ....... // Step 2, save the canvas' layers ....... // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers ....... if (drawTop) { matrix.setScale(1, fadeHeight * topFadeStrength); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); } ..... // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); ...... }
View顯示內容由一下幾個部分組成:
背景(background)
自己的內容(content)
子視圖
邊界漸變效果(fade effect),上下左右四個邊界均可能會有漸變效果,代碼中只顯示了上邊界的漸變效果繪製。
邊框或者裝飾效果(decorations),好比滾動條
舉個例子吧,咱們都知道在佈局文件中,TextView
有兩個比較重要的屬性:background
,text
。background
能夠設置TextView的背景,而text
則是設置要繪製字體內容。
<TextView android:layout_width="wrap_content" android:background="@drawable/ic_launcher" android:text="Test" android:layout_height="wrap_content" />
mScrollX
和mScrollY
對除了自己內容外的部分的繪製都有影響。只是不會影響視圖背景的繪製。
咱們都知道,在Android的視圖中,佈局相關的數值都是有方向性的,好比mLeft
,mTop
。
由上圖咱們能夠知道,Android視圖座標的原點在屏幕的左上方,x軸正方向是向右,y軸正方向是向下。因此,當你將mLeft
和mTop
的數值加10而且重繪視圖時,視圖會向右下移動。
那麼mScrollY
和mScrollX
也在這樣一個座標域中嗎?它們的正方向和mTop
和mLeft
是同樣的嗎?是的,它們屬於同一個座標域,方向性相同。
可是若是你將mScrollX
和mScrollY
的數值都增大10,而後調用invalidate()
從新繪製界面的話,你會發現視圖中的內容都向左上角移動啦!
這是怎麼回事呢?從概念上你能夠先這樣解:mScrollX
和mScrollY
改變致使View的可視區域的移動,並非致使View的視圖區域的移動。
View的視圖區域至關於無限大的,你能夠在onDraw
函數中的canvas
中繪製任意大的圖像,可是你會發現,最終屏幕上顯示出來的只會是一部分,由於View自身還有大小概念,也就是measure
和layout
時,視圖會被設置長寬還有界面中位置,這樣的話,視圖可視區域就被肯定啦。
作一個形象的比喻。View的可視區域就是一面牆上的窗戶,View的視圖區域就至關於牆後邊的優美景色。牆外風光無線,可是你只能看到窗戶中的景色。若是窗戶變大啦,外邊風景不變,你看到的景色就大了一點;若是窗戶向右下角移動了一段距離,你就會發現外邊的景色好像是向左上角"移動"了一段距離。
這兩個函數是用來滾動視圖的API
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(); } } } public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
你們看源代碼很容易就理解了兩者的做用和區別:scrollTo
就是直接改變mScrollX
和mScrollY
;而scrollBy
則是給mScrollX
和mScrollY
加上增量。
上邊這兩個函數都是請求視圖從新繪製的API,可是兩者的使用有些區別。
invalidate
必須在主線程(UI Thread)中調用,而postInvalidate
能夠在非主線程(Non UI Thread)中調用。
除此以外,兩者還有點小區別。
調用invalidate
時,它會檢查上一次請求的UI重繪是否完成,若是沒有完成的話,那麼它就什麼都不作。
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { ..... //DRAWN和HAS_BOUNDS是否被設置爲1,說明上一次請求執行的UI繪製已經完成,那麼能夠再次請求執行 if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || (fullInvalidate && isOpaque() != mLastIsOpaque)) { ...... final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); p.invalidateChild(this, damage);//TODO:這是invalidate執行的主體 ..... } }
而postInvalidate
則不會這樣,它是向主線程發送個Message
,而後handleMessage
時,調用了invalidate()
函數。
//View.java public void postInvalidateDelayed(long delayMilliseconds) { ... attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); ... } // ViewRootImpl 發送Message public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); } // ViewRootImpl 處理Message public void handleMessage(Message msg) { switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; } }
因此,兩者的調用時機仍是有區別的,就好比使用Scroller
進行視圖滾動時,兩者的調用就有所不一樣。
以後還有會兩篇博文,一篇是《Android Scroll詳解(二):OverScroller實戰》講解具體代碼實現,另一篇是《Android Scroll詳解(三):Android 繪製過程詳解》主要是從滾動角度理解Android繪製過程,請你們多多關注啊。