自定義View之onLayout方法學習

關於onLayout的學習,也是在基於View視圖樹的遞歸調用實現。

本篇想說明的是,不去深究View源碼關於onLayout,以及layout方法的實現原理。知道大概,目的是在會用。但是需要了解並掌握View內部關於onLayout方法的相關API使用。

在ViewGroup中,onLayout是一個抽象方法,所以如果繼承了ViewGroup類,除了添加構造方法外,還必須要重寫onLayout方法。

/** 
 * 當這個view和其子view被分配一個大小和位置時,被layout調用。 
 * @param changed 當前View的大小和位置改變了 
 * @param left 左部位置(相對於父視圖) 
 * @param top 頂部位置(相對於父視圖) 
 * @param right 右部位置(相對於父視圖) 
 * @param bottom 底部位置(相對於父視圖) 
 */  
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
此方法是在ViewGroup中的layout方法所調用:

@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;
        }
    }
而ViewGroup中的layout方法,是繼承自View類當中的layout方法,然後在看一下

View類中layout的方法實現。

/** 
 * 給View和其所有子View分配大小和位置 
 * 
 * 這是佈局的第二個階段(第一個階段是測量)。在這個階段中,每個父視圖需要去調用layout去爲他所有的子視圖確定位置 
 * 派生的子類不應該重寫layout方法,應該重寫onLayout方法,在onlayout方法中應該去調用每一個view的layout 
 */  
public void layout(int l, int t, int r, int b) {  
    // 將當前視圖的左上右下記錄爲old值(參數中傳入的爲新的l,t,r,b值)  
    int oldL = mLeft;  
    int oldT = mTop;  
    int oldB = mBottom;  
    int oldR = mRight;  
      
    // setFrame方法的作用就是將新傳入的ltrb屬性賦值給View,然後判斷當前View大小和位置是否發生了變化並返回  
    boolean changed = setFrame(l, t, r, b);  
      
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  
        // 調用onLayout回調方法,具體實現由重寫了onLayout方法的ViewGroup的子類去實現(後面詳細說明)  
        onLayout(changed, l, t, r, b);  
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;  
  
        // 調用所有重寫了onLayoutChange監聽的方法,通知View大小和位置發生了改變  
        ListenerInfo li = mListenerInfo;  
        if (li != null && li.mOnLayoutChangeListeners != null) {  
            ArrayList<OnLayoutChangeListener> listenersCopy =  
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();  
            int numListeners = listenersCopy.size();  
            for (int i = 0; i < numListeners; ++i) {  
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);  
            }  
        }  
    }  
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;  
} 
在這裏邊也有onLayout()方法,在View中的onLayout();中是一個實現了個空方法。

 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

關於layout,onLayout在View與ViewGroup中就這四個。

我們在ViewGroup中重寫onLayout的目的

就是設置當前View與其所有的子View,在ViewGroup(或其繼承ViewGroup的Layout)父佈局當中的位置。

在onLayout方法中,我們可以獲取當前佈局,在父佈局當中的位置:

getLeft();//子View左邊界到父view左邊界的距離
getTop();//子View上邊界到父view上邊界的距離
getRight();//子View右邊界到父View左邊界距離
getBottom();//子View下邊界到父View上邊界距離。

還可以在onMeasure()方法執行後在onLayout方法中,測量出當前佈局View,在父佈局中的
寬度:getWidth();//在onLayout()方法中取得相對在父佈局中的寬、高
高度:getHeight();//取值在getMeasuredHeight()方法之後。
此處要注意區分子View的

childView.getMeasuredWidth();//在onMeasure()方法之後取得View的實際寬、高

childView.getMeasuredHeight();

兩個方法取值的區別就在如果View的大小沒有超過父佈局View的大小,二者是相等的。getWidth() == childView.getMeasuredWidth();

如果childView寬、高大於了父佈局的寬、高 則:getWidth() + 超出區域 = childView.getMeasuredWidth();

來源:官方文檔。詳見jafsldkfj博客。

而計算viewChild在父佈局中的位置,可以通過:

viewChild.layout();方法實現View的佈局定位。

在ViewGroup中重寫onLayout方法,在onLayout方法中對子View,viewChild.layout();佈局定位,而layout中又調用了onLayout()方法

所以實現了View視圖樹遞歸調用的原理。跟onMeasure類似。

(在ViewGroup中,layout()方法是個final類型,所以不能被子類調用。在ViewGroup中,沒有measure,onMeasure方法。)

(在View中,measure()方法是個final類型,也不能被子類調用。但是layout是一個普通方法,onLayout是一個實現了空方法。)

添加點一Tip:
① 在代碼註釋文檔中/**{@hide}*/ 表示該方法不向子類開發,不提供外部使用。順便可以學習下Java doc註釋。


最終的學習,還是爲了在項目中靈活運用,實踐。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章