View 繪製體系知識梳理(4) 繪製過程之 Layout 詳解

澤毛發表於2017-12-21

一、佈局的起點 - performTraversals

和前面分析測量過程類似,整個佈局的起點也是在ViewRootImplperformTraversals當中:

private void performTraversals() {
    ......
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    ......
}
複製程式碼

可以看到,佈局過程會參考前面一步測量的結果,和測量過程的measureonMeasure方法很像,佈局過程也有兩個方法layoutonLayout,參考前面的分析,我們先對這兩個方法進行介紹:

1.1 layoutonLayout

  • 對於View
  • layout方法是public的,和measure不同的是,它不是final的,也就是說,繼承於View的控制元件可以重寫layout方法,但是我們一般不這麼做,因為在它的layout方法中又呼叫了onLayout,所以繼承於View的控制元件一般是通過重寫onLayout來實現一些邏輯。
     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);
         }
         //...
     }
    複製程式碼
  • onLayout方法是一個空實現。
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}
複製程式碼
  • 對於ViewGroup
  • 它重寫了View中的layout,並把它設為final,也就是說繼承於ViewGroup的控制元件,不能重寫layout,在layout方法中,又會呼叫super.layout,也就是Viewlayout
    @Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }
    複製程式碼
  • onLayout方法重寫View中的onLayout方法,並把它宣告成了abstract,也就是說,所有繼承於ViewGroup的控制元件,都必須實現onLayout方法
    @Override
    protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
複製程式碼
  • 對於繼承於View的控制元件 例如TextView,我們一般不會重寫layout,而是在onLayout中進行簡單的處理。
  • 對於繼承於ViewGroup的控制元件
  • 例如LinearLayout,由於ViewGroup的作用是為了包裹子View,而每個控制元件由於作用不同,佈局的方法自然也不同,這也是為了安卓要求每個繼承於ViewGroup的控制元件都必須實現onLayout方法的原因。
  • 因為ViewGrouplayout方法不可以重寫,因此,當我們通過父容器呼叫一個繼承於ViewGroup的控制元件的layout方法時,它最終會回撥到該控制元件的onLayout方法。

1.2 佈局onLayout(boolean changed, int l, int t, int r, int b)引數說明

onLayout方法被回撥時,傳入了上面這四個引數,經過前面的分析,我們知道onLayout是通過layout方法呼叫過來,而layout方法父容器呼叫的,父容器在呼叫的時候是根據自己的座標來計算出寬高,並把自己的位置的左上角當作是(0,0)點,重新決定它所屬子View的座標,因此這個矩形的四個座標是相對於父容器的座標值

1.3 佈局的遍歷過程

雖然layout在某些方面和measure有所不同,但是它們有一點是共通的,那就是:它們都是作為整個從根節點到葉節點傳遞的紐帶,當從父容器到子View傳遞的過程中,我們不直接呼叫onLayout,而是呼叫layoutonMeasure在測量過程中負責兩件事:它自己的測量和它的子View的測量,而onLayout不同:它並不負責自己的佈局,這是由它的父容器決定的,它僅僅負責自己的下一級子View的佈局。 再回到文章最開始的點,起點是通過mView也就是DecorViewlayout方法觸發的,而DecorView實際上是一個FrameLayout,經過前面的分析,我們應該直接去看FrameLayoutonLayout方法:

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }
複製程式碼

可以看到,在它的onLayout當中,又呼叫了它的子Viewlayout,那麼這時候就分為兩種情況,一種是該child是繼承於ViewGroup的控制元件並且它有子節點,那麼child.layout方法最終又會呼叫到child.onLayout,在裡面,它同樣會進行和FrameLayout所類似的操作,繼續呼叫child的子節點的layout;另一種是該childView或者是繼承於View的控制元件或者是它是繼承於ViewGroup的控制元件但是沒有子節點,那麼到該child節點的佈局遍歷過程就結束了。

1.4 小結

通過分析測量和佈局的過程,它們基於一個思想,把傳遞實現這兩個邏輯分開在不同的函式中處理,在實現當中,再去決定是否要傳遞

相關文章