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也讲完了。
---------------------
作者:林序
来源:CSDN
原文:https://blog.csdn.net/lin962792501/article/details/84892325
版权声明:本文为博主原创文章,转载请附上博文链接!

posted @ 2019-06-12 18:26  天涯海角路  阅读(215)  评论(0)    收藏  举报