自定义流式布局

自定义流式布局,能够设置margin、padding。子view实现自动换行markdown

image.png

/**
 * FlowLayout自适应容器实现
 */
public class FlowLayout extends ViewGroup {

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


    /**
     * (1)什么时候换行
     * 从效果图中能够看到,FlowLayout的布局是一行行的,若是当前行已经放不下下一个控件,那就把这个控件移到下一行显示。
     * 因此咱们要有个变量来计算当前行已经占据的宽度,以判断剩下的空间是否还能容得下下一个控件。
     * (2)、如何获得FlowLayout的宽度
     * FlowLayout的宽度是全部行宽度的最大值,因此咱们要记录下每一行的所占据的宽度值,进而找到全部值中的最大值。
     * (3)、如何获得FlowLayout的高度
     * 很显然,FlowLayout的高度是每一行高度的总和,而每一行的高度则是取该行中全部控件高度的最大值。
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d(FlowLayout.class.getSimpleName(), "onMeasure");
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);

        int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        //every line width
        int lineWidth = 0;
        //every line height
        int lineHeight = 0;
        //FlowLayout width
        int width = 0;
        //FlowLayout height
        int height = 0;


        int count = getChildCount();

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
            int marginLeft = layoutParams.leftMargin;
            int marginRight = layoutParams.rightMargin;
            int marginTop = layoutParams.topMargin;
            int marginBottom = layoutParams.bottomMargin;

            measureChild(child, widthMeasureSpec, heightMeasureSpec);

            int childWidth = child.getMeasuredWidth() + marginLeft + marginRight;
            int childHeight = child.getMeasuredHeight() + marginTop + marginBottom;


            if (lineWidth + childWidth + paddingRight + paddingLeft > measureWidth) {
                //须要换行
                width = Math.max(childWidth, lineWidth);
                height += lineHeight;
                //由于因为盛不下当前控件,而将此控件调到下一行,因此将此控件的高度和宽度初始化给lineHeight、lineWidth
                lineWidth = childWidth;
                lineHeight = childHeight;
            } else {
                // 不然累加值lineWidth,lineHeight取最大高度
                lineWidth += childWidth;
                lineHeight = Math.max(lineHeight, childHeight);
            }

            //添加最后一行
            if (i == count - 1) {
                width = Math.max(lineWidth, width);
                height += lineHeight;
            }
        }

        //增长内间距
        height = height + paddingTop + paddingBottom;
        width = width + paddingLeft + paddingRight;

        setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth : width, (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight : height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int count = getChildCount();
        //累加当前行的行宽
        int lineWidth = 0;
        //当前行的行高
        int lineHeight = 0;
        //当前坐标的top坐标和left坐标
        int top = paddingTop, left = paddingLeft;

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);

            MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
            int marginLeft = layoutParams.leftMargin;
            int marginRight = layoutParams.rightMargin;
            int marginTop = layoutParams.topMargin;
            int marginBottom = layoutParams.bottomMargin;

            int childWidth = child.getMeasuredWidth() + marginLeft + marginRight;
            int childHeight = child.getMeasuredHeight() + marginTop + marginBottom;

            if (childWidth + lineWidth + paddingRight + paddingLeft> getMeasuredWidth()) {
                //若是换行,当前控件将跑到下一行,从最左边开始,因此left就是paddingLeft,而top则须要加上上一行的行高,才是这个控件的top点;
                top += lineHeight;
                left = paddingLeft;

                //一样,从新初始化lineHeight和lineWidth
                lineHeight = childHeight;
                lineWidth = childWidth;

            } else {
                // 不然累加值lineWidth,lineHeight取最大高度
                lineWidth += childWidth;
                lineHeight = Math.max(lineHeight, childHeight);
            }

            int lp = left + marginLeft;
            int tp = top + marginTop;
            int rp = lp + child.getMeasuredWidth();
            int bp = tp + child.getMeasuredHeight();


            child.layout(lp, tp, rp, bp);

            //将left置为下一子控件的起始点
            left += childWidth;
        }
    }

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

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

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }
}
复制代码

其中须要重写 重写generateLayoutParams()函数,才能实现子View设置Marginide

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

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

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }
复制代码

原理以下: 首先,在container在初始化子控件时,会调用LayoutParams generateLayoutParams(LayoutParams p)来为子控件生成对应的布局属性,但默认只是生成layout_width和layout_height因此对应的布局参数,即在正常状况下的generateLayoutParams()函数生成的LayoutParams实例是不可以取到margin值的.函数

/**
*从指定的XML中获取对应的layout_width和layout_height值
*/
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new LayoutParams(getContext(), attrs);
}
/*
*若是要使用默认的构造方法,就生成layout_width="wrap_content"、layout_height="wrap_content"对应的参数
*/
protected LayoutParams generateDefaultLayoutParams() {
     return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
复制代码
相关文章
相关标签/搜索