自定义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

猜你喜欢

转载自blog.csdn.net/rjgcszlc/article/details/81007284