android--------自定义控件 之 ViewGroup

时间:2022-04-09 05:27:34

前面几篇讲了自定义控件的组合控件,地址:http://www.cnblogs.com/zhangqie/p/8985612.html

 

今天这篇博文主要来说说 自定义控件的 ViewGroup。

 

什么是ViewGroup?

ViewGroup是一种容器。它包含零个或以上的View及子View

 

ViewGroup有什么作用?

ViewGroup内部可以用来存放多个View控件,并且根据自身的测量模式,来测量View子控件,并且决定View子控件的位置。这在下面会逐步讲解它是怎么测量及决定子控件大小和位置的。

 

自定义控件

public class FlowLayoutb extends ViewGroup {

    private int horizontolSpacing;
    private int verticalSpacing;

    public FlowLayoutb(Context context) {
        super(context);
        init(context);
    }

    public FlowLayoutb(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private Line currentline;// 当前的行
    private int useWidth = 0;// 当前行使用的宽度
    private List<Line> mLines = new ArrayList<Line>();
    private int width;

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

    private void init(Context context) {
        horizontolSpacing = Util.dip2px(13, context);
        verticalSpacing = Util.dip2px(13, context);
    }

    // 测量 当前控件Flowlayout
    // 父类是有义务测量每个子View的
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mLines.clear();
        currentline = null;
        useWidth = 0;

        /**
         * 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式
         */
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        // 计算出所有的childView的宽和高
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop(); // 获取到宽和高
        int childeWidthMode;
        int childeHeightMode;
        //  为了测量每个子View 需要指定每个子View测量规则
        childeWidthMode = widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : widthMode;
        childeHeightMode = heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : heightMode;
        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childeWidthMode, width);
        int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childeHeightMode, height);
        currentline = new Line();// 创建了第一行
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            // 测量每个子View
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            int measuredWidth = child.getMeasuredWidth();
            useWidth += measuredWidth;// 让当前行加上使用的长度
            if (useWidth <= width) {
                currentline.addChild(child);//这时候证明当前的子View是可以放进当前的行里,放进去
                useWidth += horizontolSpacing;
            } else {
                //换行
                newLine(child);
            }
        }

        if (!mLines.contains(currentline)) {
            mLines.add(currentline);// 添加最后一行
        }
        int totalheight = 0;
        for (Line line : mLines) {
            totalheight += line.getHeight();
        }
        totalheight += verticalSpacing * (mLines.size() - 1) + getPaddingTop() + getPaddingBottom();

        System.out.println(totalheight);
        setMeasuredDimension(width + getPaddingLeft() + getPaddingRight(), resolveSize(totalheight, heightMeasureSpec));
    }

    private void newLine(View child) {
        mLines.add(currentline);// 记录之前的行
        currentline = new Line();// 创建新的一行
        currentline.addChild(child);
        useWidth = currentline.lineWidth;
    }

    private class Line {
        int height = 0; //当前行的高度
        int lineWidth = 0;
        private List<View> children = new ArrayList<View>();

        /**
         * 添加一个子View
         *
         * @param child
         */
        public void addChild(View child) {
            children.add(child);
            if (child.getMeasuredHeight() > height) {
                height = child.getMeasuredHeight();
            }
            lineWidth += child.getMeasuredWidth();
        }

        public int getHeight() {
            return height;
        }

        /**
         * 返回子View的数量
         *
         * @return
         */
        public int getChildCount() {
            return children.size();
        }

        public void layout(int l, int t) {
            lineWidth += horizontolSpacing * (children.size() - 1);
            int surplusChild = 0;
            int surplus = width - lineWidth;
            if (surplus > 0 && children.size() > 0) {
                surplusChild = surplus / children.size();
            }
            for (int i = 0; i < children.size(); i++) {
                View child = children.get(i);
                child.layout(l, t, l + child.getMeasuredWidth() + surplusChild, t + child.getMeasuredHeight());
                l += child.getMeasuredWidth() + surplusChild;
                l += horizontolSpacing;
            }
        }

    }

    // 分配每个子View的位置
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        l += getPaddingLeft();
        t += getPaddingTop();
        for (int i = 0; i < mLines.size(); i++) {
            Line line = mLines.get(i);
            line.layout(l, t);  //交给每一行去分配
            t += line.getHeight() + verticalSpacing;
        }
    }

}

 

 

自定义ViewGroup的步骤:

  1. 继承ViewGroup,覆盖构造方法
  2. 重写onMeasure方法测量子控件和自身宽高
  3. 实现onLayout方法摆放子控件

 

 

效果图:

android--------自定义控件 之 ViewGroup  android--------自定义控件 之 ViewGroup

 

 

 

源码地址:https://github.com/DickyQie/android-custom-control

 

参考资料:

https://blog.csdn.net/shineflowers/article/details/48055879

https://blog.csdn.net/zxt0601/article/details/50533658

https://blog.csdn.net/shakespeare001/article/details/51089453