系列文章傳送門 (持續更新中..) :android
自定義控件(一) Activity的構成(PhoneWindow、DecorView)canvas
在上一篇中咱們詳細分析了 View 工做三大流程中最複雜的 measure 流程, 掌握了 measure 流程後, layout 和 draw 流程就相對比較簡單些了。源碼分析
layout
方法被父容器調用後它的位置將被肯定下來, 而後它在 onLayout
中遍歷全部的子元素並調用它的 layout
方法對子元素進行擺放, 而在 layout 中 onLayout
方法又被調用, 如此反覆直到佈局完成。簡單講:layout 方法肯定 View 自己的位置, onLayout 肯定全部子元素的位置佈局
在看源碼以前,先提出一個問題, View 的 getWidth()
和 getMeasuredWidth()
有什麼區別?post
public void layout(int l, int t, int r, int b) {
...
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame (l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...
}
複製代碼
layout 方法中, 先調用 setFrame()
給本身的四個頂點賦值, 就肯定了本身的位置。而後調用 onLayout
方法對子元素進行擺放優化
protected boolean setFrame(int left, int top, int right, int bottom) {
...
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
...
}
複製代碼
看到沒,mLeft、mTop 、mRight 、mBottom
就是這個 View 的四個頂點,當四個頂點的值唄肯定,View 的位置就擺放完了。 因爲在 View 中 onLayout()
方法是空實現, ViewGroup 的 onLayout()
是抽象方法, 因此就挑一個 ViewGroup 經常使用的子類 FrameLayout 看一下 (其它都相似, 本身能夠去看下):ui
#FrameLayout - onLayoutthis
protected void onLayout(boolean changed, int left, int top, int right, int bottom){
layoutChildren(left, top, right, bottom, false);
}
複製代碼
onLayout
的參數直接傳給 layoutChildren
,繼續走:
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
...
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
...
child.layout(childLeft, childTop, childLeft + width, childTop + height);
...
複製代碼
很明顯, 在 layoutChildren 遍歷全部的子元素, 並調用其 layout 方法來擺放子元素,這樣父容器在 layout 方法中完成本身的擺放後,經過 onLayout 方法去遍歷調用子元素的 layout 方法,子元素又會經過 layout 方法肯定本身的位置,這樣一層一層傳遞下去從而完成整個 View 樹的 layout 過程。
width
和 height
, 其實就是這個 view 的測量寬/高。前面分析了 layout 中傳入的參數會對 View 的四個頂點(mLeft、mTop 、mRight 、mBottom)
賦值來肯定位置,這裏咱們來看一下 getWidth()
的返回值:public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
複製代碼
結合咱們剛剛的源碼分析不難看出來,mRight - mLeft 和 mBottom - mTop 的返回值不就分別是 view 的測量寬高麼, 因此在系統 View 的默認實現中,以及開發中咱們能夠直接認爲 getWidth() = getMeasuredWidth(),只是賦值時間不一樣, getHeight 和 getMeasuredHeight 同理, getMeasuredWidth()
是在 onMeasure()
方法中執行完成測量流程後並保存尺寸的時候被賦值,getWidth()
是在 layout
方法中肯定本身位置的時候被賦值。
固然也存在兩種狀況會出現不相等:一種是某些極端狀況系統須要屢次執行measure流程,這時則除了最後一次measure,前幾回的measure結果就可能存在不相等。另外一種則是在 onLayout() 中調用 layout 時, 對傳入的四個頂點值作了一些運算處理, 則這兩個值也是不相等的,以下
protected void onLayout(boolean changed, int left, int top, int right, int bottom){
...
child.layout(childLeft, childTop, childLeft + width + 100, childTop + height + 100);
...
}
// 或重寫 layout 方法
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r + 100, b + 100);
}
複製代碼
繪製過程就是將 View 繪製到屏幕上, 它分爲4步:
具體從 draw
方法中能夠明瞭的看出來:
public void draw(Canvas canvas) {
...
drawBackground(canvas);
...
onDraw(canvas);
...
dispatchDraw(canvas);
...
onDrawForeground(canvas);
...
}
複製代碼
繪製過程的傳遞是經過 dispatchDraw
來實現,dispatchDraw
中會遍歷全部子元素的 draw
方法,如此反覆下去直到繪製完成。
這是 View 的一個特殊方法,具體看源碼:
/**
* If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */ public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); } 複製代碼
從註釋大體能看出來,若是一個 View 不須要對本身進行任何繪製,設置這個標誌位爲 true 後,系統會進行相應的優化,即繞過 draw()
方法,換而直接執行 dispatchDraw()
,以此來簡化繪製流程。默認狀況下 View 沒有設置這個標誌位, 而 ViewGroup 默認會啓動這個標誌位。
dispatchDraw()
之外的任何一個繪製方法內繪製內容,你顯示的關閉這個 WILL_NOT_DRAW 這個標誌位:View.setWillNotDraw(false)
;以爲有用的話,點個贊再走唄~