1. 程式人生 > >自定義ViewGroup之自定義佈局的實現

自定義ViewGroup之自定義佈局的實現

圖片預覽
這裡寫圖片描述

1. 分析

1. 自定義簡易FrameLayout 分別左上,右上,左下,右下4個子View
2. 自定義簡易LinearLayout,實現橫向和縱向佈局
3. 自定義簡易RelativeLayout,實現layout_alignParentXXX方法

2. 實現原理

1. 自定義ViewGroup主要是複寫onMeasure測量每一個子View的寬高,複寫onLayout計算每一個子View的上下左右間距
2. 自定義一些屬性值
3. 複寫generateLayoutParams,計算margin值

3. 簡易FrameLayout實現

1. onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //分別獲取寬高的測量模式 int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); //記錄如果是wrap_content時設定的寬和高
int width, height; //左右的高度 int lHeight = 0,rHeight = 0; //上下的寬度 int tWidth = 0, bWidth = 0; //測量所有子孩子的寬高 measureChildren(widthMeasureSpec,heightMeasureSpec); for (int i = 0; i < getChildCount(); i++) { View childView = getChildAt(i); int childWidth = childView.getMeasuredWidth(); int
childHeight = childView.getMeasuredHeight(); MarginLayoutParams mParams = (MarginLayoutParams) childView.getLayoutParams(); if(i ==0 || i == 1){ tWidth += childWidth + mParams.leftMargin + mParams.rightMargin; } if(i == 2 || i == 3){ bWidth += childWidth + mParams.leftMargin + mParams.rightMargin; } if(i == 0 || i== 2){ lHeight += childHeight + mParams.topMargin + mParams.bottomMargin; } if(i == 1 || i == 3){ rHeight += childHeight + mParams.topMargin + mParams.bottomMargin; } } //wrap_content 時取寬高的最大值 width = Math.max(tWidth,bWidth); height = Math.max(lHeight,rHeight); int measureWidth = widthMode == MeasureSpec.EXACTLY ? widthSize : width; int measureHeight = heightMode == MeasureSpec.EXACTLY ? heightSize : height; setMeasuredDimension(measureWidth,measureHeight); } 2. onLayout 需要注意的一個地方是最好計算一下父View的pading值 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { for (int i = 0; i < getChildCount(); i++) { View childView = getChildAt(i); int childWidth = childView.getMeasuredWidth(); int childHeight = childView.getMeasuredHeight(); MarginLayoutParams cParams = (MarginLayoutParams) childView.getLayoutParams(); int childLeft = 0,childTop = 0,childRight = 0,childBottom = 0; switch (i){ case 0: childLeft = cParams.leftMargin; childTop = cParams.topMargin+ getPaddingTop(); break; case 1: childLeft = getMeasuredWidth() - childWidth - cParams.rightMargin; childTop = cParams.topMargin + getPaddingTop(); break; case 2: childLeft = cParams.leftMargin; childTop = getMeasuredHeight() - childHeight - cParams.bottomMargin - getPaddingBottom() - getPaddingTop(); break; case 3: childLeft = getMeasuredWidth() - childWidth - cParams.rightMargin; childTop = getMeasuredHeight() - childHeight - cParams.bottomMargin -getPaddingTop() - getPaddingBottom(); break; default: } childRight = childLeft + childWidth; childBottom = childTop + childHeight; childView.layout(childLeft,childTop,childRight,childBottom); } }

4. 簡易LinearLayout實現

分橫向和豎向
橫向的時候當寬高為wrap_content的時候,寬度累加+左右padding值
高度取子View的最大高度
豎向的時候當寬高為wrap_content的時候,寬度取子View中的最大寬度,高度累加+上下padding值
onLayout主要是計運算元View的left,top,right,bottom的距離,細心一點就好

1. onMeasure

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

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

    //記錄如果是wrap_content時設定的寬和高
    int width, height;
    int totalWidth = 0;
    int totalHeight = 0;

    //測量所有子孩子的寬高
    measureChildren(widthMeasureSpec,heightMeasureSpec);

    for (int i = 0; i < getChildCount(); i++) {

        View childView = getChildAt(i);
        int childWidth = childView.getMeasuredWidth();
        int childHeight = childView.getMeasuredHeight();
        MarginLayoutParams mParams = (MarginLayoutParams) childView.getLayoutParams();

        if("horizontal".equals(mOrientation)){
            //橫向高度取子孩子中高度最大值 帶上margin和父View的padding值
            int cHeight = childHeight + mParams.topMargin + mParams.bottomMargin + this.getPaddingBottom() + this.getPaddingTop();
            if(cHeight > totalHeight){
                totalHeight = cHeight;
            }
            //橫向第一個子孩子和最後一個子孩子需要帶上父View的padding值
            if(i == 0){
                totalWidth+= getPaddingLeft();
            }else if(i == getChildCount() - 1){
                totalWidth += getPaddingRight();
            }
            totalWidth += childWidth + mParams.leftMargin + mParams.rightMargin;

        }else {

            //豎向寬度取其中一個子孩子的最大高度
            int cWidth = childWidth + mParams.leftMargin + mParams.rightMargin + this.getPaddingLeft() + this.getPaddingRight();
            if (cWidth > totalWidth) {
                totalWidth = cWidth;
            }

            //豎向的第一子孩子和最後一個子孩子需要帶上父View的padding值
            if(i == 0){
                totalHeight += getPaddingTop();
            }else if(i == getChildCount() - 1){
                totalHeight += getPaddingBottom();
            }
            totalHeight += childHeight + mParams.topMargin + mParams.bottomMargin;

        }
    }

    width = totalWidth;
    height = totalHeight;
    int measureWidth  = widthMode == MeasureSpec.EXACTLY ? widthSize : width;
    int measureHeight  = heightMode == MeasureSpec.EXACTLY ? heightSize : height;

    setMeasuredDimension(measureWidth,measureHeight);
}

2. onLayout

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

    int childLeft ;
    int childTop ;
    int childRight ;
    int childBottom ;
    int lastTotalHeight = 0;
    int lastTotalWidth = 0;
    for (int i = 0; i < getChildCount(); i++) {

        View childView = getChildAt(i);
        int childWidth = childView.getMeasuredWidth();
        int childHeight = childView.getMeasuredHeight();
        MarginLayoutParams cParams = (MarginLayoutParams) childView.getLayoutParams();

        if("horizontal".equals(mOrientation)) {
            //橫向第一個帶上父View的左邊padding值
            if(i == 0){
                childLeft = cParams.leftMargin + getPaddingLeft();
            }else {
                childLeft = lastTotalWidth + cParams.leftMargin;
            }
            //橫向最後一個帶上父View的右邊padding值
            if(i == getChildCount() - 1) {
                childRight = childLeft + childWidth+ getPaddingRight();
            }else{
                childRight = childLeft + childWidth;
            }
            lastTotalWidth = childRight;

            //橫向上下帶上上下padding值
            childTop = cParams.topMargin + getPaddingTop();
            childBottom = childTop + childHeight + getPaddingBottom();
        }else{
            //豎向左右帶上左右padding值
            childLeft = cParams.leftMargin + getPaddingLeft();
            childRight = childLeft + childWidth + getPaddingRight();

            //豎向第一個帶上父View的上邊padding值
            if(i == 0){
                childTop =  cParams.topMargin + getPaddingTop();
            }else {
                childTop = lastTotalHeight + cParams.topMargin;
            }

            ////豎向最後個帶上父View的下邊padding值
            if(i == getChildCount() - 1){
                childBottom = childTop + childHeight+ getPaddingBottom();
            }else {
                childBottom = childTop + childHeight;
            }
            lastTotalHeight = childBottom;
        }

        childView.layout(childLeft,childTop,childRight,childBottom);
    }
}

5. 簡易RelativeLayout實現

onMeasure
當寬高為wrap_content的時候,寬度取子View的最大寬度
高度取子View的最大高度,記得帶上左右padding值
onLayout
根據自定義的子View的不同位置layout_position,計運算元View的left,top,right,bottom的距離。

1. onMeasure

     @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);

    //用於計算wrap_content時候的寬高
    int width = 0;
    int height = 0;

    for (int i = 0; i < getChildCount(); i++) {

        View child = getChildAt(i);
        //測量每一個子孩子
        measureChild(child,widthMeasureSpec,heightMeasureSpec);

        CustomLayoutParam lp = (CustomLayoutParam) child.getLayoutParams();
        int lrMargin = lp.leftMargin + lp.rightMargin;
        int tbMargin = lp.topMargin + lp.bottomMargin;

        int childWidth = child.getMeasuredWidth() + lrMargin;
        int childHeight = child.getMeasuredHeight() + tbMargin;

        //獲取子孩子中最大的子孩子寬度
        if(width < childWidth){
            width = childWidth;
        }

        //獲取子孩子總最大的子孩子高度
        if(height < childHeight){
            height = childHeight;
        }
    }

    //帶上父View的上下左右的pading
    width += getPaddingLeft() + getPaddingRight();
    height += getPaddingTop() + getPaddingBottom();

    int measureWidth = widthMode == MeasureSpec.AT_MOST ? width : widthSize;
    int measureHeight = heightMode == MeasureSpec.AT_MOST ? height : heightSize;

    setMeasuredDimension(measureWidth,measureHeight);
}     

2. onLayout

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

    int pWidth = getMeasuredWidth();
    int pHeight = getMeasuredHeight();

    for (int i = 0; i < getChildCount(); i++) {

        View child = getChildAt(i);
        CustomLayoutParam lp = (CustomLayoutParam) child.getLayoutParams();
        int lrMargin = lp.leftMargin + lp.rightMargin;
        int tbMargin = lp.topMargin + lp.bottomMargin;
        int cWidthAndMargin = child.getMeasuredWidth() + lrMargin;
        int cHeightAndMargin = child.getMeasuredHeight() + tbMargin;
        int cWidth = child.getMeasuredWidth();
        int cHeight = child.getMeasuredHeight();
        int left = 0;
        int top = 0;
        int right = 0;
        int bottom = 0;
        switch (lp.getPosition()){
            case "leftTop":
                left = lp.leftMargin + getPaddingLeft();
                top = lp.topMargin + getPaddingTop();
                break;
            case "rightTop":
                left = pWidth - cWidthAndMargin- getPaddingRight();
                top = lp.topMargin + getPaddingTop();
                right += getPaddingRight();
                break;
            case "leftBottom":
                left = lp.leftMargin + getPaddingLeft();
                top = pHeight - cHeightAndMargin;
                bottom += getPaddingBottom();
                break;
            case "rightBottom":
                left = pWidth - cWidthAndMargin- getPaddingLeft();
                top = pHeight - cHeightAndMargin;
                bottom += getPaddingBottom();
                break;
            case "horizontalCenter":
                left = (pWidth - cWidth) / 2;
                top = lp.topMargin + getPaddingTop();
                break;
            case "verticalCenter":
                left = lp.leftMargin + getPaddingLeft();
                top = (pHeight - cHeight) / 2;
                break;
            case "rightVerticalCenter":
                left = pWidth - cWidthAndMargin- getPaddingLeft();
                top = (pHeight - cHeight) / 2;

                break;
            case "bottomHorizontalCenter":
                left = (pWidth - cWidth) / 2;
                top = pHeight - cHeightAndMargin;
                bottom += getPaddingBottom();
                break;
            case "center":
                left = (pWidth - cWidth) / 2;
                top = (pHeight - cHeight) / 2;
                break;
        }

        right += left + child.getMeasuredWidth();
        bottom += top + child.getMeasuredHeight();
        child.layout(left,top,right,bottom);
    }
}

6. 複寫generateLayoutParams方法

@Override
 public LayoutParams generateLayoutParams(AttributeSet attrs) {
     return new CustomLayoutParam(getContext(),attrs);
 }

 public class CustomLayoutParam extends ViewGroup.MarginLayoutParams{

    /**
     * leftTop
     * rightTop
     * horizontalCenter
     * verticalCenter
     * rightVerticalCenter
     * bottomHorizontalCenter
     * center
     * leftBottom
     * rightBottom
     */
    private String mPosition;

    public CustomLayoutParam(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyRelativeLayout);
        mPosition = typedArray.getString(R.styleable.MyRelativeLayout_layout_position);
        if(TextUtils.isEmpty(mPosition)){
            mPosition = "leftTop";
        }
        typedArray.recycle();
    }

    public String getPosition() {
        return mPosition;
    }
}

7. 專案原始碼下載

後面統一提供下載地址

8. 聯絡方式

QQ:1509815887