編輯:關於Android編程
轉載請注明出處:http://blog.csdn.net/singwhatiwanna/article/details/38426471(來自singwhatiwanna的csdn博客)
Android View系統解析系列:
Android View系統解析(上)
介紹View的基礎知識、View的滑動、彈性滑動、滑動沖突解決方式、事件分發等
Android View系統解析(下)
介紹View的Framework層原理、View的measure / layout / draw三大流程和一些高級技巧
本次主要介紹下半部分,提綱如下
View的繪制過程
measure/layout/draw 工作流程
識別 MeasureSpec 並能夠 make 合適的 MeasureSpec
在渲染前獲取 View 的寬高
構造特殊的 View
自定義View
自定義View分類
自定義 View 須知
ViewRoot
對應於 ViewRootImpl 類,是連接 WindowManager 和 DecorView 的紐帶。
ActivityThread 中當 activity 對象被創建好後,會將 DecorView 加入到 Window中同時完成 ViewRootImpl 的創建並建立和 DecorView 的聯系。
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
view 的繪制流程從 ViewRoot 的 performTraversals 開始,代碼流程是這樣的:
performMeasure -> measure -> onMeasure
performLayout -> layout -> onLayout
performDraw -> draw -> onDraw
由下圖可知,DecorView作為頂級View,一般情況下它有上下兩部分組成(具體情況會和api版本以及Theme有關),上面是title,下面是content,在activity中我們調用setContentView所設置的view其實就是被加到content中,而如何得到content呢,可以這樣:ViewGroup group = findViewById(R.android.id.content),如何得到我們所設置的view呢,可以這樣:group.getChildAt(0)。同時,通過源碼我們可以知道,DecorView其實是一個FrameLayout。這裡要說明的一點是View層的大部分事件都是從DecorView傳遞到我們的view中的。
MeasureSpec 這裡分析下頂級容器DecorView的MeasureSpec的產生過程 childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); 上述代碼描述了DecorView的MeasureSpec的產生過程,為了更清晰地了解,我們繼續看下去 根據它的LayoutParams中的寬高的參數來分, LayoutParams.MATCH_PARENT:其模式為精確模式,大小就是窗口的大小 LayoutParams.WRAP_CONTENT:其模式為最大模式,大小不定,但是不能超過窗口的大小 固定大小(比如100dp):其模式為精確模式,大小為LayoutParams中指定的大小 針對上表,這裡再做一下具體的說明。前面已經提到,對於應用層 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自 view#onMeasure 的默認實現 注意: 那麼如何重寫onMeasure從而讓view支持wrap_content呢?請參看下面的典型代碼,需要注意的是,代碼中的mWidth和mHeight指的是view在wrap_content下的內部規格,而這一規格(寬高)應該由自定義view內部來指定。 這是一個比較有意義的問題,或者說有難度的問題,問題的背景為:有時候我們需要在view渲染前去獲取其寬高,典型的情形是,我們想在onCreate、onStart、onResume中去獲取view的寬高。如果大家嘗試過,會發現,這個時候view還沒有measure好,寬高都為0,那到底該怎麼做才能正確獲取其寬高呢,下面給出三種方法 Activity/View#onWindowFocusChanged :這個方法表明,view已經初始化完畢了,寬高已經准備好了 前兩種方法都比較好理解也比較簡單,這裡主要介紹下第三種方法的詳細用法: 采用 view.measure 去提前獲取 view 的寬高,根據 view 的 layoutParams 來分 注意到(1 << 30) - 1,通過分析MeasureSpec的實現可以知道,view的尺寸使用30位二進制表示的,也就是說最大是30個1即 2^30 - 1,也就是(1 << 30) - 1,在最大化模式下,我們用view理論上能支持的最大值去構造MeasureSpec是合理的。 關於view的measure,網絡上有兩個錯誤的用法,如下,為什麼說是錯誤的,首先違背了系統的內部實現規范(因為無法通過錯誤的MeasureSpec去得出合法的SpecMode從而導致measure出錯),其次不能保證一定能 measure 出正確的結果。 第一種錯誤用法 第二種錯誤用法 layout 的主要作用 問題:如何讓 getWidth 和 getMeasuredWidth 返回的值不一樣? draw 的大致流程 繼承 View 重寫 onDraw
封裝了父容器對 view 的布局上的限制,內部提供了寬高的信息( SpecMode 、 SpecSize ),SpecSize是指在某種SpecMode下的參考尺寸,其中SpecMode 有如下三種:
UNSPECIFIED
父容器不對 view 有任何限制,要多大給多大
EXACTLY<喎?/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjxiciAvPri4yN3G99LRvq287LLis/YgdmlldyDL+dDo0qq1xLTz0KE8YnIgLz48c3Ryb25nPkFUX01PU1Q8L3N0cm9uZz48YnIgLz64uMjdxvfWuLaowcvSu7j2tPPQoaOsIHZpZXcgtcS089ChsrvE3LTz09rV4rj21rU8YnIgLz48c3Ryb25nPk1lYXN1cmVTcGVjcyC1xNLi0uU8L3N0cm9uZz48YnIgLz7NqLn9vasgU3BlY01vZGUgus0gU3BlY1NpemUgtPKw/LPJ0ru49iBpbnQg1rW/ydLUsdzD4rn9tuC1xLbUz/PE2rTmt9bF5KOszqrBy7e9seOy2df3o6zG5MzhuanBy7TysPwgLyC94rD8t723qDxiciAvPjwvcD48aDM+PHN0cm9uZz5NZWFzdXJlU3BlYyC1xMq1z9Y8L3N0cm9uZz48L2gzPjxwPk1lYXN1cmVTcGVjPC9wPjxwPrT6se3Su7j2IDMyIM67IGludCDWtTxiciAvPrjfIDIgzru0+rHtIFNwZWNNb2RlIKOstc0gMzAgzru0+rHtIFNwZWNTaXplPGJyIC8+PC9wPjxwPjwvcD48cD7PwsPmz8i/tNK7z8JNZWFzdXJlU3BlYyDE2rK/tcTSu9Cps6PBv7XEtqjS5aOszai5/c/Cw+a1xLT6wuujrNOmuMOyu8TRwO294k1lYXN1cmVTcGVjtcS5pNf31K3A7TwvcD48cD48L3A+PHByZSBjbGFzcz0="brush:java;">private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}MeasureSpec 與 LayoutParams
對於 DecorView ,其 MeasureSpec 由窗口的尺寸和其自身的LayoutParams 來共同確定
對於應用層 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 來共同決定
MeasureSpec 一旦確定後, onMeasure 中就可以確定自身的寬高MeasureSpec-DecorView
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
通過上述代碼,頂級容器DecorView的MeasureSpec的產生過程就很明確了,具體來說其遵守如下規則:MeasureSpec- 應用層 View
關於應用層View,這裡是指我們布局中的view,其MeasureSpec的創建遵循下表中的規則
身的 LayoutParams 來共同決定,那麼針對不同的父容器和view本身不同的LayoutParams,view就可以有多種MeasureSpec。這裡簡單說下,當view采用固定寬高的時候,不管父容器的MeasureSpec是什麼,view的MeasureSpec都是精確模式並且其大小遵循Layoutparams中的大小;當view的寬高是match_parent時,這個時候如果父容器的模式是精准模式,那麼view也是精准模式並且其大小是父容器的剩余空間,如果父容器是最大模式,那麼view也是最大模式並且其大小不會超過父容器的剩余空間;當view的寬高是wrap_content時,不管父容器的模式是精准還是最大化,view的模式總是最大化並且大小不能超過父容器的剩余空間。可能大家會發現,在我們的分析中漏掉了Unspecified模式,這個模式主要用於系統內部多次measure的情況下,一般來說,我們不需要關注此模式。支持 View 的 wrap_content
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
通過 onDraw 派生的 View ,需要重寫 onMeasure 並設置 wrap_content 時的自
身大小,否則使用 wrap_content 就相當於用 match_parent 。
原因分析:見上面的表格protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mHeight);
}
}
View的measure 流程
view 的 measure 流程
簡單,直接完成
ViewGroup 的 measure 流程
除了完成自己的 measure ,還會遍歷去調用所有 child 的measure 方法,各個 child 再遞歸去執行這個流程
measure 的直接結果:
getMeasuredWidth/Height 可以正確地獲取到
注:某些情況下,系統可能需要多次 measure 才能確定大小在渲染前獲取 View 的寬高
view.post(runnable) :通過post可以將一個runnable投遞到消息隊列的尾部,然後等待looper調用此runnable的時候,view也已經初始化好了
view.measure(int widthMeasureSpec, int heightMeasureSpec) :通過手動去measure來視圖得到view的寬高
match_parent
直接放棄,無法 measure 出具體的寬高
具體的數值( dp/px )
比如寬高都是 100px ,如下 measure :
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
wrap_content
如下 measure :
int widthMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) - 1, MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) - 1, MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec, heightMeasureSpec);
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
view.measure(widthMeasureSpec, heightMeasureSpec);
view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)View 的 layout 過程
ViewGroup 用來確定子元素的位置。
流程
當 viewgroup 的位置被確定後,它在 onLayout 會遍歷所有的 child 並調用其 layout 。在 layout 中 onLayout 會被調用。
關鍵方法
public void layout(int l, int t, int r, int b)
onLayout(changed, l, t, r, b)構造特殊的 View
private void setChildFrame(View child, int left, int top, int measuredWidth, int measureHeight) {
child.layout(left, top, left + measuredWidth, top + measureHeight);
}
int width = right - left;
int height = bottom - top
方法
在父容器的 onLayout 中通過 child.layout 來放置 view 到任意位置
在自己的 onLayout 中修改 mLeft/mRight/mTop/mBottomView 的 draw 過程
a. 畫背景 background.draw(canvas)
b. 繪制自己( onDraw )
c. 繪制 children ( dispatchDraw )
d. 繪制裝飾( onDrawScrollBars )
備注:
dispatchDraw 會遍歷調用所有 child 的 draw ,如此 draw 事件就一層層地傳遞了下去二 自定義 View
自定義View類型
繼承 ViewGroup 派生特定的 Layout
繼承特定的 View (比如 TextView , ListView )
繼承特定的 Layout (比如 LinearLayout )自定義View須知
讓 view 支持 wrap_content
如果有必要,讓你的 view 支持 padding
盡量不要在 view 中使用 Handler ,沒必要
view 中如果有線程或者動畫,需要及時停止,參考View#onDetachedFromWindow
view 帶有滑動嵌套情形時,需要處理好滑動沖突更多資料
http://blog.csdn.net/singwhatiwanna
https://github.com/singwhatiwanna
看了這篇文章Android Studio如何查看資源或者函數在哪些類中被引用,知道了快捷鍵失效的原因,其中有一個原因就是快捷鍵沖突,那如何查看快捷鍵哪些項沖突了呢? A
Android中對操作的文件主要可以分為:File、XML、SharedPreference。這篇博客主要介紹對File的操作:1、MainActivity保存到SD卡中
一、對Canvas進行操作對Canvas的一系列操作,是指對Canvas進行旋轉、平移、縮放等操作。這些操作可以讓Canvas對象使用起來更加便捷。二、Canvas平移/
簡介:Volley是Google I/O 2013上Google官方發布的一款Android平台上的網絡通信庫。以前的網絡請求,要考慮開啟線程、內存洩漏、性能等等復雜的問