自定义ViewGroup系列---CornerLayout案例

本篇来讲解自定义ViewGroup的案例CornerLayout的实现,该ViewGroup的效果是在4个角包含4个子view。
如下图示可以帮我们理解该ViewGroup的效果。
在这里插入图片描述
我们来分析下要实现这个ViewGroup的思路:
1,onMeasure() 测量流程
①,如果CornerLayout是wrap_content的,那么其宽度是 Math.max(A.width,C.width) + Math.max(B.width,D.width),其高度是Math.max(A.height , B.height) + Math.max(C.height , D.height)
②,如果CornerLayout是math_parent的或者具体数值,那就不用计算了,直接使用onMeasure中传入的数值了。

2,onLayout() 布局思路
CornerLayout的子view具有明显的特征,排列在四个角落,我们要判断当前子view是应该是属于哪个角落的,比如chille(0) 是左上角的;child(1)是右上角的;child(2)是左下角的;child(3)是右小角的。只要确定子view属于哪个角落,就能方便的给子view定位了

③,onDraw绘制容器
CornerLayout没有特别的边框什么的要求,无需重写onDraw绘制

我们按照上面的步骤给出CornerLayout的第一版代码(后面我们还会继续优化)

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

    measureChildren(widthMeasureSpec, heightMeasureSpec);
    setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));

}

private int measureWidth(int widthMeasureSpec){
    int width = 0;
    int size = MeasureSpec.getSize(widthMeasureSpec);
    int mode = MeasureSpec.getMode(widthMeasureSpec);
    Log.e("cyy","size:"+size+"  mode:"+mode);
    if(mode == MeasureSpec.EXACTLY){
        width = size;
    }else if(mode == MeasureSpec.AT_MOST){
        int aWidth = 0, bWidth = 0, cWidth = 0, dWidth = 0;
        int aMargin = 0, bMargin = 0, cMargin = 0, dMargin = 0;
        for(int i = 0; i<getChildCount(); i++){
            if(i == 0){
                aWidth = getChildAt(i).getMeasuredWidth();
            }else if(i == 1){
                bWidth = getChildAt(i).getMeasuredWidth();
            }else if(i == 2){
                cWidth = getChildAt(i).getMeasuredWidth();
            }else if(i == 3){
                dWidth = getChildAt(i).getMeasuredWidth();
            }
        }
        width = Math.max(aWidth,cWidth)+Math.max(bWidth,dWidth)
    }
    return width;
}
private int measureHeight(int heightMeasureSpec){

   int height = 0;
   int size = MeasureSpec.getSize(heightMeasureSpec);
   int mode = MeasureSpec.getMode(heightMeasureSpec);

   if(mode == MeasureSpec.EXACTLY){
       height = size;
   }else if(mode == MeasureSpec.AT_MOST){
       int aHeight = 0, bHeight = 0, cHeight = 0, dHeight = 0;
       int aMargin = 0, bMargin = 0, cMargin = 0, dMargin = 0;
       for(int i = 0; i<getChildCount(); i++){
           if(i == 0){
               aHeight = getChildAt(i).getMeasuredHeight();
           }else if(i == 1){
               bHeight = getChildAt(i).getMeasuredHeight();
           }else if(i == 2){
               cHeight = getChildAt(i).getMeasuredHeight();
           }else if(i == 3){
               dHeight = getChildAt(i).getMeasuredHeight();
           }
       }
       height = Math.max(aHeight,bHeight)+Math.max(cHeight,dHeight);
   }
   return height;
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    for(int i = 0 ; i< getChildCount() ; i++){
        View child = getChildAt(i);
        //注意padding,margin的处理
        MarginLayoutParams marginLayoutParams = (MarginLayoutParams) getChildAt(i).getLayoutParams();
        int leftMargin = marginLayoutParams.leftMargin;
        int rightMargin = marginLayoutParams.rightMargin;
        int topMargin = marginLayoutParams.topMargin;
        int bottomMargin = marginLayoutParams.bottomMargin;

        if(i == 0){
            //定位到左上角
            child.layout(0,0,child.getMeasuredWidth(),child.getMeasuredHeight());
        }else if(i == 1){
            //定位到右上角
            child.layout(getMeasuredWidth() - child.getMeasuredWidth() ,0, getMeasuredWidth(), child.getMeasuredHeight());
        }else if(i == 2){
            //定位到左下角
            child.layout(0,getMeasuredHeight() - child.getMeasuredHeight(),child.getMeasuredWidth(),getMeasuredHeight());
        }else if(i == 3){
            //定位到右下角
            child.layout(getMeasuredWidth() - child.getMeasuredWidth() ,getMeasuredHeight() - child.getMeasuredHeight(),getMeasuredWidth(), getMeasuredHeight());
        }
    }
}

上面给出了CornerLayout的measure和layout的代码,在layout.xml中加载CornerLayout并未其添加4个子view,运行起来效果如下:

<com.example.cyy.customerview.view.CornerLayoutViewGroup
        android:layout_width="350dp"
        android:layout_height="wrap_content"
        android:background="#abc">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="AAAAAA"
           android:background="#000000"
            android:layout_marginStart="20dp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="BBBBBBBBBBBBBBBBBBB"
            android:background="#ffff00"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="CCCC"
            android:background="#00ff00"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="DDDDDDDDDDD"
            android:background="#0000ff"/>

在这里插入图片描述

可以看到ABCD四个view分散在CornerLayout的四个角落。

看似完美了~~~~~

然而,如果我们尝试去给CornerLayout添加padding值就会发现问题了,padding不生效。。。。
为什么padding不生效了,回头去看看我看的onMeasure和OnLayout方法就知道了,因为我们并没有处理padding啊,在测量CornerLayout时,我们只是根据子view的宽高来设置CornerLayout的宽高,并没有处理padding值,在给子view定位时,也是只根据子view的宽高来定位,如图左上角的子view:
child.layout(0,0,child.getMeasuredWidth(),child.getMeasuredHeight());我们并没有将CornerLayout的padding考虑进去,子view当然就是紧紧贴着CornerLayout的边缘显示了。

所以现在我们要把CornerLayout的padding值的控制添加进去,我们给出添加padding控制后ConerLayout变动的部分:

private int measureWidth(int widthMeasureSpec){
	width = Math.max(aWidth,cWidth)+Math.max(bWidth,dWidth)
                    + getPaddingStart() + getPaddingEnd();
}

private int measureHeight(int heightMeasureSpec){
	height = Math.max(aHeight,bHeight)+Math.max(cHeight,dHeight)
                    + getPaddingTop() + getPaddingBottom()
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    for(int i = 0 ; i< getChildCount() ; i++){
        View child = getChildAt(i);
        if(i == 0){
            //定位到左上角
            child.layout(getPaddingStart(),getPaddingTop(), getPaddingStart()+child.getMeasuredWidth(), child.getMeasuredHeight()+getPaddingTop());
        }else if(i == 1){
            //定位到右上角
            child.layout(getMeasuredWidth() - child.getMeasuredWidth() - getPaddingEnd() ,getPaddingTop(),getMeasuredWidth() - getPaddingEnd(),child.getMeasuredHeight()+getPaddingTop());
        }else if(i == 2){
            //定位到左下角
            child.layout(getPaddingStart(), getMeasuredHeight() - child.getMeasuredHeight() - getPaddingBottom(),child.getMeasuredWidth() + getPaddingStart(),getMeasuredHeight() - getPaddingBottom());
        }else if(i == 3){
            //定位到右下角
            child.layout(getMeasuredWidth() - child.getMeasuredWidth() - getPaddingEnd(),getMeasuredHeight() - child.getMeasuredHeight() - getPaddingBottom(), getMeasuredWidth() - getPaddingEnd(),getMeasuredHeight() - getPaddingBottom());
        }
    }
}

现在在尝试给CornerLayout添加padding属性,就会生效了。

<com.example.cyy.customerview.view.CornerLayoutViewGroup
        android:layout_width="350dp"
        android:layout_height="wrap_content"
        android:paddingStart="20dp"
        android:paddingRight="20dp"
        android:paddingTop="10dp"
        android:paddingBottom="30dp"
        android:background="#abc">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="AAAAAA"
           android:background="#000000"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="BBBBBBBBBBBBBBBBBBB"
            android:background="#ffff00"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="CCCC"
            android:background="#00ff00"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="DDDDDDDDDDD"
            android:background="#0000ff"/>


    </com.example.cyy.customerview.view.CornerLayoutViewGroup>

在这里插入图片描述
完美支持了padding属性。
但是到这里我们的CornerLayout还不是最完美的,尽管已经支持了padding属性,但是对于子组件的margin属性还是不支持的,我们可以尝试给A控件添加一个margin_start,我们期待A控件会向左移动一段距离,但是运行后发现并不是这样的,子view的margin属性没有生效。

发现了问题,下面就是解决问题。

我们在上篇说过,在向viewGroup中addView时有3个重载的方法:
public void addView(View child)
public void addView(View child, int index)
public void addView(View child, int index, LayoutParams params)

我们去查看这三个方法的源码可知,其最终都是调用了有3个参数的addView方法,当我们调用前两个方法时,ViewGroup会通过 generateDefaultLayoutParams方法来生成一个默认的LayoutParams。
在这里插入图片描述
我们来看下ViewGroup的generateDefaultLayoutParams方法:

protected LayoutParams generateDefaultLayoutParams() {
  return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
 }

ViewGroup的generateDefaultLayoutParams是创建了ViewGroup.LayoutParams的参数对象,该对象是不支持获取子View的margin属性的,而继承ViewGroup.LayoutParams的MarginLayoutParams 这个对象是支持子view的margin属性获取的。
public static class MarginLayoutParams extends ViewGroup.LayoutParams
所以如果我们想要CornerLayout的子组件支持marign属性的读取,那就需要重写CornerLayout的generateDefaultLayoutParams()了,另外还需要重新另两个方法:
1,创建 LayoutParams(或子类)对象,通过 attrs 可以读取到布局文件中的自定义属性值, 该方法必须重写;
public LayoutParams generateLayoutParams(AttributeSet attrs)
2,创建 LayoutParams(或子类)对象,可以重用参数 p,该方法建议重写。
protected LayoutParams generateLayoutParams(LayoutParams p)

ConrnerLayout支持margin属性的相关代码如下:

//为了支持margin属性,添加如下内容
@Override
 protected LayoutParams generateLayoutParams(LayoutParams p) {
     return new MarginLayoutParams(p);
 }

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

 @Override
 protected LayoutParams generateDefaultLayoutParams() {
     return new MarginLayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
 }

现在CornerLayout的默认LayoutParams就是MarginLayoutParams,而这个参数对象支持获取子view的margin,我们在CornerLayout中就能得到子view的margin值,然后要将子view的margin加入到ViewGroup的测量和布局流程中。那必然这个margin值会影响最终CornerLayout的测量的大小和子view的位置。

如果要考虑 margin,则将影响以下几个方面:

  • 影响 onMeasure()方法测量的容器尺寸;
  • 影响 onLayout()方法对子组件的定位;
  • 必须为子组件供默认的 MarginLayoutParams(或其子类)

我们在上一次添加支持padding属性的基础上在次在添加CornerLayout支持子view的margin属性,至此CornerLayout的最终版就产生了,现在给出CornerLayout最终版本的代码。

public class CornerLayoutViewGroup extends ViewGroup {
    public CornerLayoutViewGroup(Context context) {
        super(context);
    }

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

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

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for(int i = 0 ; i< getChildCount() ; i++){
            View child = getChildAt(i);
            //注意padding,margin的处理
            MarginLayoutParams marginLayoutParams = (MarginLayoutParams) getChildAt(i).getLayoutParams();
            int leftMargin = marginLayoutParams.leftMargin;
            int rightMargin = marginLayoutParams.rightMargin;
            int topMargin = marginLayoutParams.topMargin;
            int bottomMargin = marginLayoutParams.bottomMargin;

            if(i == 0){
                //定位到左上角
                child.layout(getPaddingStart()+leftMargin,
                             getPaddingTop()+topMargin,
                          getPaddingStart()+child.getMeasuredWidth()+leftMargin,
                          child.getMeasuredHeight()+getPaddingTop()+topMargin);

            }else if(i == 1){
                //定位到右上角
                child.layout(getMeasuredWidth() - child.getMeasuredWidth() - getPaddingEnd() - rightMargin,
                                getPaddingTop()+topMargin,
                             getMeasuredWidth() - getPaddingEnd() - rightMargin,
                             child.getMeasuredHeight()+getPaddingTop()+topMargin);
            }else if(i == 2){
                //定位到左下角
                child.layout(getPaddingStart() + leftMargin,
                        getMeasuredHeight() - child.getMeasuredHeight() - getPaddingBottom() - bottomMargin,
                        child.getMeasuredWidth() + getPaddingStart() + leftMargin,
                        getMeasuredHeight() - getPaddingBottom() - bottomMargin);
            }else if(i == 3){
                //定位到右下角
                child.layout(getMeasuredWidth() - child.getMeasuredWidth() - getPaddingEnd() - rightMargin,
                        getMeasuredHeight() - child.getMeasuredHeight() - getPaddingBottom() - bottomMargin,
                        getMeasuredWidth() - getPaddingEnd() - rightMargin,
                        getMeasuredHeight() - getPaddingBottom() - bottomMargin);
            }
        }
    }

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

        measureChildren(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));

    }

    private int measureWidth(int widthMeasureSpec){
        int width = 0;
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        Log.e("cyy","size:"+size+"  mode:"+mode);
        if(mode == MeasureSpec.EXACTLY){
            width = size;
        }else if(mode == MeasureSpec.AT_MOST){
            int aWidth = 0, bWidth = 0, cWidth = 0, dWidth = 0;
            int aMargin = 0, bMargin = 0, cMargin = 0, dMargin = 0;
            for(int i = 0; i<getChildCount(); i++){
                MarginLayoutParams layoutParams = (MarginLayoutParams) getChildAt(i).getLayoutParams();

                if(i == 0){
                    aWidth = getChildAt(i).getMeasuredWidth();
                    aMargin = layoutParams.leftMargin + layoutParams.rightMargin;
                }else if(i == 1){
                    bWidth = getChildAt(i).getMeasuredWidth();
                    bMargin = layoutParams.leftMargin + layoutParams.rightMargin;
                }else if(i == 2){
                    cWidth = getChildAt(i).getMeasuredWidth();
                    cMargin = layoutParams.leftMargin + layoutParams.rightMargin;
                }else if(i == 3){
                    dWidth = getChildAt(i).getMeasuredWidth();
                    dMargin = layoutParams.leftMargin + layoutParams.rightMargin;
                }
            }
            width = Math.max(aWidth,cWidth)+Math.max(bWidth,dWidth)
                    + getPaddingStart() + getPaddingEnd()
                    + Math.max(aMargin,cMargin)+Math.max(bMargin,dMargin);

        }

        return width;

    }


    private int measureHeight(int heightMeasureSpec){

        int height = 0;
        int size = MeasureSpec.getSize(heightMeasureSpec);
        int mode = MeasureSpec.getMode(heightMeasureSpec);

        if(mode == MeasureSpec.EXACTLY){
            height = size;
        }else if(mode == MeasureSpec.AT_MOST){
            int aHeight = 0, bHeight = 0, cHeight = 0, dHeight = 0;
            int aMargin = 0, bMargin = 0, cMargin = 0, dMargin = 0;
            for(int i = 0; i<getChildCount(); i++){
                MarginLayoutParams layoutParams = (MarginLayoutParams) getChildAt(i).getLayoutParams();

                if(i == 0){
                    aHeight = getChildAt(i).getMeasuredHeight();
                    aMargin = layoutParams.topMargin + layoutParams.bottomMargin;
                }else if(i == 1){
                    bHeight = getChildAt(i).getMeasuredHeight();
                    bMargin = layoutParams.topMargin + layoutParams.bottomMargin;
                }else if(i == 2){
                    cHeight = getChildAt(i).getMeasuredHeight();
                    cMargin = layoutParams.topMargin + layoutParams.bottomMargin;
                }else if(i == 3){
                    dHeight = getChildAt(i).getMeasuredHeight();
                    dMargin = layoutParams.topMargin + layoutParams.bottomMargin;
                }
            }
            height = Math.max(aHeight,bHeight)+Math.max(cHeight,dHeight)
                    + getPaddingTop() + getPaddingBottom()
                    +Math.max(aMargin,bMargin)+Math.max(cMargin,dMargin);
        }

        return height;

    }

    //为了支持margin属性,添加如下内容
    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

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

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
    }
}

现在我们可以给A组件添加一个magin,在次运行看下效果

<com.example.cyy.customerview.view.CornerLayoutViewGroup
        android:layout_width="350dp"
        android:layout_height="wrap_content"
        android:paddingStart="20dp"
        android:paddingRight="20dp"
        android:paddingTop="10dp"
        android:paddingBottom="30dp"
        android:background="#abc">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="AAAAAA"
           android:background="#000000"
            android:layout_marginStart="20dp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="BBBBBBBBBBBBBBBBBBB"
            android:background="#ffff00"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="CCCC"
            android:background="#00ff00"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="DDDDDDDDDDD"
            android:background="#0000ff"/>


    </com.example.cyy.customerview.view.CornerLayoutViewGroup>

在这里插入图片描述

完美~~~~

DONE
此系列后续持续更新。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值