Android View總結
作者:threezj
原文地址:Android View總結
關於Android View控制元件
Android中控制元件大致被分為兩類ViewGroup,View。ViewGroup作為容器管理View。Android檢視,是類似於Dom樹的架構。父檢視負責測量定位繪製等操作。我們經常在用的findViewById
方法代價昂貴的原因,就是因為他負責至上而下遍歷整棵控制元件樹,來尋找View例項,在重複操作中儘量少用。現在在用的很多控制元件都是直接或者間接繼承自View的,如下圖。
Android UI介面架構
每個Activity包含一個PhoneWindow
物件,PhoneWindow
設定DecorView
為應用視窗的根檢視。在裡面就是熟悉的TitleView
和ContentView
,沒錯,平時使用的setContentView()
就是設定的ContentView
。
Android是如何繪製View的?
當一個Activity啟動時,會被要求繪製出它的佈局。Android框架會處理這個請求,當然前提是Activity提供了合理的佈局。繪製從根檢視開始,從上至下遍歷整棵檢視樹,每一個ViewGroup
負責讓自己的子View
被繪製,每一個View
負責繪製自己,通過draw()
方法,繪製過程分三步走。
- Measure
- Layout
- Draw
整個繪製流程是在ViewRoot
中的performTraversals()
方法展開的。部分原始碼如下。
private void performTraversals() {
......
//最外層的根檢視的widthMeasureSpec和heightMeasureSpec由來
//lp.width和lp.height在建立ViewGroup例項時等於MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
}
在繪製之前當然要知道view的尺寸和繪製。所以先進行measu
和layout
(測量和定位),如下圖。
Measure過程
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//....
//回撥onMeasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
//more
}
計算view的實際大小,獲得高寬存入mMeasuredHeight
和mMeasureWidth
,measure(int, int)
傳入的兩個引數。MeasureSpec
是一個32位int值,高2位為測量的模式,低30位為測量的大小。測量的模式可以分為以下三種。
EXACTLY
精確值模式,當layout_width
或layout_height
指定為具體數值,或者為match_parent
時,系統使用EXACTLY。AT_MOST
最大值模式,指定為wrap_content
時,控制元件的尺寸不能超過父控制元件允許的最大尺寸。UNSPECIFIED
不指定測量模式,View想多大就多大,一般不太使用。
根據上面的原始碼可知,measure
方法不可被重寫,自定義時需要重寫的是onMeasure
方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
檢視原始碼可知,最終的高寬是呼叫setMeasuredDimension()
設定的,如果不重寫,預設是直接呼叫getDefaultSize
獲取尺寸的。
使用View的getMeasuredWidth()
和getMeasuredHeight()
方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程之後被呼叫才能返回有效值。
Layout過程
Layout
方法就是用來確定view佈局的位置,就好像你知道了一件東西的大小以後,總要知道位置才能畫上去。
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
layout獲取四個引數,左,上,右,下座標,相對於父檢視而言。這裡可以看到,使用了剛剛測量的寬和高。
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
.....
onLayout(changed, l, t, r, b);
.....
}
通過setFrame
設定座標。如果座標改變過了,則重新進行定位。如果是View物件,那麼onLayout
是個空方法。因為定位是由ViewGroup確定的。
當layout結束以後getWidth()
與getHeight()
才會返回正確的值。
這裡出現一個問題,getWidth/Height()
和 getMeasuredWidth/Height()
有什麼區別?
-
getWidth()
:View在設定好佈局後View的寬度。 -
getMeasuredWidth()
:對View上的內容進行測量後得到的View內容佔據的寬度。
Draw過程
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);
}
// skip step 2 & 5 if possible (common case)
......
// Step 2, save the canvas' layers
......
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
......
// 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);
......
}
重點是第三步呼叫onDraw
方法。其它幾步都是繪製一些邊邊角角的東西比如背景、scrollBar之類的。其中dispatchDraw,是用來遞迴呼叫子View,如果沒有則不需要。
onDraw
方法是需要自己實現的,因為每個控制元件繪製的內容不同。主要用canvas物件進行繪製,這裡就不說了。
參考資料
相關文章
- Android View 使用總結AndroidView
- 自定義view總結View
- materialized view 的總結ZedView
- Android自定義View之事件分發機制總結AndroidView事件
- Android技能樹 — View小結AndroidView
- Android ViewTreeObserver使用總結及獲得View高度的幾種方法AndroidViewServer
- 學習總結--View 的移動View
- qt model view 程式設計總結QTView程式設計
- 自定義View以及事件分發總結View事件
- Android技能樹 — View事件體系小結AndroidView事件
- Android ViewAndroidView
- android webview總結AndroidWebView
- Android面試總結Android面試
- Android 總結 bookAndroid
- Android總結1Android
- Android自定義View:View(二)AndroidView
- Android View 系統 1 - View樹AndroidView
- android view 分析AndroidView
- Android中Service總結Android
- Android面試最新總結Android面試
- Android Handler面試總結Android面試
- android 面試題總結Android面試題
- android WebView總結(轉)AndroidWebView
- Android總結篇系列:Android ServiceAndroid
- Android XML佈局報錯:android/view/View$OnUnhandledKeyEventListenerAndroidXMLView
- Android自定義view-自繪ViewAndroidView
- Sql Server關於indexed view索引檢視的總結SQLServerIndexView索引
- flutter自定義View(CustomPainter) 之 canvas的方法總結FlutterViewAICanvas
- 面試題總結-Android部分面試題Android
- android IO流操作總結Android
- Android鍵盤操作總結Android
- Android中的NDK總結Android
- Android-繪圖機制總結Android繪圖
- Android 熱修復總結Android
- Android面試總結(updating)Android面試
- Android效能優化總結Android優化
- android textview問題總結AndroidTextView
- android截圖方法總結Android