編輯:關於android開發
在前邊的文章中,我們已經對Android觸摸事件處理有了大致的了解,並且詳細探討了MotionEvent的相關用法。對之前文章中的知識還不是很了解的同學,請閱讀《Android MotionEvent詳解》
今天,我們就來探討一下Android中界面滾動效果的相關機制,本篇文章主要講解一下滾動相關的知識點,之後的文章會涉及實際的代碼和原理。希望大家閱讀完這篇文章之後,能夠了解或者掌握一下知識:
mScrollX和mScrollY對視圖顯示的影響scrollTo和scrollBy的使用invalidate和postInvalidate的區別 我們都知道,View中有兩個重要的成員變量,mScrollX,mScrollY.它們分別代表視圖內容(view content)水平方向和豎直方向的滾動距離。我們可以通過setScrollX和setScrollY來個函數來改變它們的值,從而來滾動視圖的內容。
在這裡需要強調的是,mScrollX和mScrollY會導致視圖內容(view content)變化,但是不會影響視圖背景(background)。
看到這裡同學們或許會有寫疑問,視圖的內容和背景有什麼區別呢?視圖還有哪些組成部分呢?
我們可以從View的draw方法中得知View的組成部分。
// 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顯示內容由一下幾個部分組成:
舉個例子吧,我們都知道在布局文件中,TextView有兩個比較重要的屬性:background,text。background可以設置TextView的背景,而text則是設置要繪制字體內容。
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繪制過程,請大家多多關注啊。
Android網絡編程(三)Volley用法全解析
Android網絡編程(三)Volley用法全解析 相關文章 Android網絡編程(一)HTTP協議原理 Android網絡編程(二)HttpClient與HttpUR
硅谷商城5--購物車商品選中和商品總價計算,5--購物車
硅谷商城5--購物車商品選中和商品總價計算,5--購物車 1_商品總價格計算 ①在GovaffairPager類中設置 adapter = new GovaffairP
Android活動的生存期,Android活動生存期
Android活動的生存期,Android活動生存期Activity 類中定義了七個回調方法,覆蓋了活動生命周期的每一個環節,下面我來一一介紹下這七個方法。1. onCr
Android UI:ListView,androiduilistview
Android UI:ListView,androiduilistviewSimpleAdapter是擴展性最好的適配器,可以定義各種你想要的布局,而且使用很方便。 l