自定义ViewGroup之自定义布局的实现

图片预览
这里写图片描述
1. 分析

1. 自定义简易FrameLayout 分别左上,右上,左下,右下4个子View
2. 自定义简易LinearLayout,实现横向和纵向布局
3. 自定义简易RelativeLayout,实现layout_alignParentXXX方法

    1
    2
    3

2. 实现原理

1. 自定义ViewGroup主要是复写onMeasure测量每一个子View的宽高,复写onLayout计算每一个子View的上下左右间距
2. 自定义一些属性值
3. 复写generateLayoutParams,计算margin值

    1
    2
    3

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121

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;
    }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34

7. 项目源代码下载

后面统一提供下载地址

    1

8. 联系方式

QQ:1509815887

---------------------
作者:尽人事看天意
来源:CSDN
原文:https://blog.csdn.net/rjgcszlc/article/details/81007284
版权声明:本文为博主原创文章,转载请附上博文链接!

posted @ 2019-06-17 19:19  天涯海角路  阅读(487)  评论(0)    收藏  举报