Android View框架的layout機制


概述

        Android中View框架的工作機制中,主要有三個過程:

                1、View樹的測量(measure) Android View框架的measure機制

                2、View樹的布局(layout)Android View框架的layout機制

                3、View樹的繪制(draw)Android View框架的draw機制

        View框架的工作流程為:測量每個View大小(measure)-->把每個View放置到相應的位置(layout)-->繪制每個View(draw)。

 

         本文主要講述三大流程中的layout過程。不清楚measure過程的,可以參考這篇文章 Android View框架的measure機制

 

 

 

帶着問題來思考整個layout過程。

 

1、系統為什么要有layout過程?

        View框架在經過第一步的measure過程后,成功計算了每一個View的尺寸。但是要成功的把View繪制到屏幕上,只有View的尺寸還不行,還需要准確的知道該View應該被繪制到什么位置。除此之外,對一個ViewGroup而言,還需要根據自己特定的layout規則,來正確的計算出子View的繪制位置,已達到正確的layout目的。這也就是layout過程的職責。

        該位置是View相對於父布局坐標系的相對位置,而不是以屏幕坐標系為准的絕對位置。這樣更容易保持樹型結構的遞歸性和內部自治性。而View的位置,可以無限大,超出當前ViewGroup的可視范圍,這也是通過改變View位置而實現滑動效果的原理。

 

2、layout過程都干了點什么事?

        由於View是以樹結構進行存儲,所以典型的數據操作就是遞歸操作,所以,View框架中,采用了內部自治的layout過程。

        每個葉子節點根據父節點傳遞過來的位置信息,設置自己的位置數據,每個非葉子節點,除了負責根據父節點傳遞過來的位置信息,設置自己的位置數據外(如果有父節點的話),還需要根據自己內部的layout規則(比如垂直排布等),計算出每一個子節點的位置信息,然后向子節點傳遞layout過程。

        對於ViewGroup,除了根據自己的parent傳遞的位置信息,來設置自己的位置之外,還需要根據自己的layout規則,為每一個子View計算出准確的位置(相對於子View的父布局的位置)。

        對於View,根據自己的parent傳遞的位置信息,來設置自己的位置。

        View對象的位置信息,在內部是以4個成員變量的保存的,分別是mLeft、mRight、mTop、mBottom。他們的含義如圖所示。

 

 

源代碼分析

        在View的源代碼中,提取到了下面一些關於layout過程的信息。

        我們知道,整棵View樹的根節點是DecorView,它是一個FrameLayout,所以它是一個ViewGroup,所以整棵View樹的測量是從一個ViewGroup對象的layout方法開始的。

 

View:

1、layout

/** 

分配一個位置信息到一個View上面,每個parent會調用children的layout方法來設置children的位置。最好不要覆寫該方法,有children的viewGroup,應該覆寫onLayout方法

*/

public void layout(int l, int t, int r, int b) ;

源代碼:
這里不給出,如果有興趣,自行查閱SDK。


偽代碼:

public void layout(int l, int t, int r, int b) {
    if (根據一些flag,發現需要進一步measure) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
    }
 //暫存舊的位置信息
 int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    //設置新的位置信息
    mLeft = l;
    mTop = t;
    mBottom = b;
    mRight = r;
 
    if (layout改變了 || 需要layout) {
        onLayout(changed, l, t, r, b);
          
  //回調layoutChange事件
        for (遍歷監聽對象) {
            listener.onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
        }
    }

    標記為已經執行過layout;
} 

 

 


 
2、onLayout


/** 根據布局規則,計算每一個子View的位置,View類默認是空實現。 所以這里沒有源代碼*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom);

 

ViewGroup:

 

ViewGroup中,只需要覆寫onLayout方法,來計算出每一個子View的位置,並且把layout流程傳遞給子View。

源代碼:

ViewGroup沒有實現,具體可以參考LinearLayout和RelativeLayout的onLayout方法。雖然各個具體實現都很復雜,但是基本流程是一樣的,可以參考下面的偽代碼。

偽代碼:

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    for (遍歷子View) {
        /**
        根據如下數據計算。
            1、自己當前布局規則。比如垂直排放或者水平排放。
            2、子View的測量尺寸。
            3、子View在所有子View中的位置。比如位置索引,第一個或者第二個等。
        */
        計算每一個子View的位置信息; 
        
        child.layout(上面計算出來的位置信息);
    }        
}

 


 

結論

        一般來說,自定義View,如果該View不包含子View,類似於TextView這種的,是不需要覆寫onLayout方法的。而含有子View的,比如LinearLayout這種,就需要根據自己的布局規則,來計算每一個子View的位置。

 

 

 

動手操作

        下面我們自己寫一個自定義的ViewGroup,讓它內部的每一個子View都垂直排布,並且讓每一個子View的左邊界都距離上一個子View的左邊界一定的距離,大概看起來如下圖所示:

        實際運行效果如下:

 

 

代碼如下:

public class VerticalOffsetLayout extends ViewGroup {

    private static final int OFFSET = 100;

    public VerticalOffsetLayout(Context context) {
        super(context);
    }

    public VerticalOffsetLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public VerticalOffsetLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width = 0;
        int height = 0;

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            ViewGroup.LayoutParams lp = child.getLayoutParams();
            int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
            int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
            child.measure(childWidthSpec, childHeightSpec);
        }

        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                width = widthSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    int widthAddOffset = i * OFFSET + child.getMeasuredWidth();
                    width = Math.max(width, widthAddOffset);
                }
                break;
            default:
                break;

        }

        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                height = heightSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    height = height + child.getMeasuredHeight();
                }
                break;
            default:
                break;

        }

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left = 0;
        int right = 0;
        int top = 0;
        int bottom = 0;

        int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            left = i * OFFSET;
            right = left + child.getMeasuredWidth();
            bottom = top + child.getMeasuredHeight();

            child.layout(left, top, right, bottom);

            top += child.getMeasuredHeight();
        }
    }
}

 



 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM