View的工作原理之自定义ViewGroup

        上一篇文章讲解了如何自定义普通的View,本文接着讲如何自定义ViewGroup。

        在之前的工程中创建一个类MyViewGroup,继承自ViewGroup,重写它的三个构造方法及onLayout方法,这几个方法都是要求必须实现的。

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

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

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

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childTop = 0;
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            LayoutParams params = (LayoutParams) childView.getLayoutParams();

            int marginTop = params.topMargin;
            int marginLeft = params.leftMargin;
            int marginRight = params.rightMargin;
            int marginBottom = params.bottomMargin;

            childView.layout(marginLeft,childTop+marginTop-10,
                    childView.getMeasuredWidth()+marginRight,
                    childTop+childView.getMeasuredHeight()+marginBottom);
            childTop += childView.getMeasuredHeight()+marginTop+marginBottom;
        }

    }
}

        在自定义普通的View的时候除了三个构造方法外,其他方法Android不强制要求重写,但是正常都要写onDraw方法,因为不写的话,系统不知道你要画的是什么形状的View,会导致看不到View。但是在自定义ViewGroup中,因为它需要为子View进行布局设置,所以必须重写onLayout方法。

        重写onLayout的具体逻辑根据需求而定,正常情况下,都需要获得子View的个数,然后遍历去调用layout方法去给子View进行布局。可以看到重写的MyViewGroup,我是在onLayout方法中对子View的margin值进行处理,这个我感觉也是可以的,当然在重写onMeasure方法,然后在里面处理子View的margin值也可以。上述代码非常简单,这个ViewGroup有点类似于LinearLayout的垂直布局。childTop记录了当前子View前面的自 View占据的高度。这就完成了对子View的布局。我们再来看到下面的代码:

    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
    }

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

    public static class LayoutParams extends MarginLayoutParams{

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(ViewGroup.LayoutParams params){
            super(params);
        }

        public LayoutParams(int width,int height){
            super(width,height);
        }
    }

        在onLayout方法,我们处理了子View的margin值。margin值的获取,是通过调用childView.getLayoutParams()来得到的,但是它返回的是ViewGroup.LayoutParams,我们看它的源码发现它是没有margin值对应的属性的。所以要想获得margin属性值,我们需要的是MarginLayoutParams。而要想childView.getLayoutParams()返回的是MarginLayoutParams,我们需要重写上述三个方法,使的它们返回MarginLayoutParams或者MarginLayoutParams的子类,我这里返回MarginLayoutParams的子类,其实直接返回MarginLayoutParams也是可以的。MarginLayoutParams类定义在ViewGroup中,在这个MarginLayoutParams类里面才会加载子View的margin属性值,从而进行处理。

      下面来看MyViewGroup的onMeasure方法代码:

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measureWidth = 0;
        int measureHeight = 0;
        final int childCount = getChildCount();
        measureChildren(widthMeasureSpec,heightMeasureSpec);

        int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

        if (childCount == 0){
           setMeasuredDimension(widthSpaceSize,measureHeight);
        }else if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){

            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                if (childView.getMeasuredWidth() > measureWidth){
                    measureWidth = childView.getMeasuredWidth();  //子View最大的的宽度
                }
                measureHeight += childView.getMeasuredHeight();//子View高度和
            }
            setMeasuredDimension(measureWidth,measureHeight);
        }else if(widthSpecMode == MeasureSpec.AT_MOST){

            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                if (childView.getMeasuredWidth() > measureWidth) {
                    measureWidth = childView.getMeasuredWidth();  //子View最大的的宽度
                }
            }
            setMeasuredDimension(measureWidth,heightSpaceSize);
        }else if(heightSpecMode == MeasureSpec.AT_MOST){
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                measureHeight += childView.getMeasuredHeight();//子View高度和
            }
            setMeasuredDimension(widthSpaceSize,measureHeight);
        }
    }

       代码首先获取子View的个数,然后调用ViewGroup提供的measureChildren方法开始测量子View的大小。然后根据父容器的测量模式得到MyViewGroup的宽高,在调用setMeasuredDimension方法设置它的全局宽高值。一般自定义ViewGroup不需要重写onDraw,这里我也没有记录。到这,自定义ViewGroup也讲完了。

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值