自定義控件(四) 源碼分析 layout 和 draw 流程

系列文章傳送門 (持續更新中..) :android

自定義控件(一) Activity的構成(PhoneWindow、DecorView)canvas

自定義控件(二) 從源碼分析事件分發機制bash

自定義控件(三) 源碼分析measure流程ide


在上一篇中咱們詳細分析了 View 工做三大流程中最複雜的 measure 流程, 掌握了 measure 流程後, layout 和 draw 流程就相對比較簡單些了。源碼分析

  • 在佈局流程中, 當 ViewGroup 的 layout 方法被父容器調用後它的位置將被肯定下來, 而後它在 onLayout 中遍歷全部的子元素並調用它的 layout 方法對子元素進行擺放, 而在 layout 中 onLayout 方法又被調用, 如此反覆直到佈局完成。

簡單講:layout 方法肯定 View 自己的位置, onLayout 肯定全部子元素的位置佈局

layout 過程 :

在看源碼以前,先提出一個問題, 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 過程。

  • 注意看調用 layout 傳的參數,這裏傳入的 widthheight, 其實就是這個 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);
}
複製代碼

draw 過程 :

繪製過程就是將 View 繪製到屏幕上, 它分爲4步:

  • (1) 繪製背景(私有方法不能重寫)
  • (2) 主體繪製(通常重寫此方法)
  • (3) 繪製子元素
  • (4) 繪製前景和滑動相關(繪製前景的支持是在 Android 6.0 以後)

具體從 draw 方法中能夠明瞭的看出來:

public void draw(Canvas canvas) {
	...
	drawBackground(canvas);
	...
	onDraw(canvas);
	...
	dispatchDraw(canvas);
	...
	onDrawForeground(canvas);
	...
}
複製代碼

繪製過程的傳遞是經過 dispatchDraw 來實現,dispatchDraw 中會遍歷全部子元素的 draw 方法,如此反覆下去直到繪製完成。

setWillNotDraw :

這是 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 默認會啓動這個標誌位。

  • 在實際開發中, 若是自定義控件繼承於 ViewGroup 而且自己不具有繪製功能時,就能夠開啓這個標誌讓系統進行繪製優化。 可是當明確知道一個 ViewGroup 須要在它的除 dispatchDraw() 之外的任何一個繪製方法內繪製內容,你顯示的關閉這個 WILL_NOT_DRAW 這個標誌位:View.setWillNotDraw(false)

以爲有用的話,點個贊再走唄~

相關文章
相關標籤/搜索