自适应布局FlowLayout

本文详细介绍了一个自定义的FlowLayout ViewGroup的实现过程,包括构造方法、测量(onMeasure)、布局(onLayout)等关键步骤,并提供了源码。

一图顶千言:
没错,就是这货

很明显,这是一个自定义ViewGroup,三个步骤,测量(onMeasure),布局(onLayout),绘制(onDraw,本例中用不到绘制哦);不叨叨,上代码
1.构造方法,在xml中引用,要实现2个参数的构造方法

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

2.onMeasrue,测量
FlowLayout的宽是:match_parent;高是:wrap_content,你知道的,自定义ViewGroup需要自己实现wrap_content,以及子view的margin(这个大家都很清楚了,就不赘述了。)

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //默认的高度,宽度以及模式
        int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMeasureSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMeasureMode = MeasureSpec.getMode(heightMeasureSpec);

        //viewgroup总的高度和宽度
        int height = 0;
        int width = 0;
        //每一行的宽度和高度
        int lineHeight = 0;
        int lineWidth = 0;

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childAt = getChildAt(i);
            //测量子view
            measureChild(childAt, widthMeasureSpec, heightMeasureSpec);
            //获取子view的宽高
            //LayoutParams layoutParams1 = childAt.getLayoutParams();
            MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();
            int childWidth = childAt.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
            int childHeight = childAt.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;

            //开始测量
            //需要换行
            if (lineWidth + childWidth > widthMeasureSize) {
                width = Math.max(lineWidth, childWidth);
                height += lineHeight;

                //换行后的行高和行宽
                lineWidth = childWidth;
                lineHeight = childHeight;
            } else {
                //不需要换行
                lineHeight = Math.max(lineHeight, childHeight);
                lineWidth += childWidth;

                if (i == childCount - 1) {
                    height += lineHeight;
                    width = Math.max(width, lineWidth);
                }
            }

            setMeasuredDimension(widthMeasureMode == MeasureSpec.EXACTLY ? widthMeasureSize : width
                    , heightMeasureMode == MeasureSpec.EXACTLY ? heightMeasureSize : height);
        }
    }

  //LayoutParams是viewgroup提供给子view使用的,LayoutParams无法获取margin值,MarginLayoutParams可以获取margin值
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

前面说了,我们需要自己实现margin和wrap_content
实现wrap_content:for循环,遍历所有的子view,子view的高的和就是FlowLayout的高;实现子view的margin,需要重写generateLayoutParams().往下看:

View childAt = getChildAt(i);
            //测量子view
            measureChild(childAt, widthMeasureSpec, heightMeasureSpec);
            //获取子view的宽高
            MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();

这几行代码,分别是测量子View,获取子View的LayoutParams(别告诉我你不知道LayoutParams是啥),但是,默认情况下你的代码是这个样子的:

//LayoutParams layoutParams1 = childAt.getLayoutParams();

LayoutParams 不包含margin信息,无法获取margin值,所以我们需要转成:

MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();

同时,重写:

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

原本该方法是返回LayoutParams 的,让他返回MarginLayoutParams,就是我们

MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();

中获得的MarginLayoutParams 。
onLayout 布局

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int lineWidth = 0, lineHeight = 0, left = 0, top = 0;
        for (int i = 0; i < childCount; i++) {
            View childAt = getChildAt(i);
            MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();
            int leftMargin = layoutParams.leftMargin;
            int rightMargin = layoutParams.rightMargin;
            int topMargin = layoutParams.topMargin;
            int bottomMargin = layoutParams.bottomMargin;
            int childWidth = childAt.getMeasuredWidth() + leftMargin + rightMargin;
            int childHeight = childAt.getMeasuredHeight() + topMargin + bottomMargin;

            //开始布局
            //换行
            if (lineWidth + childWidth > getMeasuredWidth()) {
                top += lineHeight;
                left = 0;
                //换行后,重新计算lineHeight,lineWidth
                lineWidth = childWidth;
                lineHeight = childHeight;
            } else {
                //不换行
                lineWidth += childWidth;
                lineHeight = Math.max(childHeight , lineHeight);
            }
            int cl = left + leftMargin;
            int cr = cl + childAt.getMeasuredWidth();
            int ct = top + topMargin;
            int cb = ct + childAt.getMeasuredHeight();
            childAt.layout(cl, ct, cr, cb);
            left += childWidth;
        }
    }

都是很简单的逻辑问题,我就不啰嗦了。

源码奉上

FlowLayoutPanel 一些应用程序需要一个布局可随窗体大小的调整或其中内容大小的改变而自动进行适当排列的窗体。在需要动态布局并且不希望在代码中显式处理 Layout 事件时,可考虑使用布局面板。 FlowLayoutPanel是.NET Framework的新增控件。顾名思义,面板可以采用Web窗体的方式给Windows窗体布局FlowLayoutPanel是一个容器,允许以垂直或水平的方式放置包含的控件。除了放置控件之外,还可以剪辑控件。放置的方向使用FlowDirection属性和FlowDirection枚举来设置。WrapContents属性确定在重新设置窗体的大小时,控件是放在下一行、下一列,还是剪辑控件。 FlowLayoutPanel 按特定的流方向排列其内容:水平或垂直。其内容可从一行换到下一行,或者从一列换到下一列。另一种情况是不换行,而是将其内容截掉。 相信大家在做WinForm项目的时候,要对大量的控件进行排序(位置摆放),这个容器肯定最受欢迎,但很遗憾的是,此容器本身虽支持Dock和Anchor属性,但不支持放入此容器内的控件的Dock和Anchor属性(自动调整宽度),也就说,但窗体伸缩,FlowLayoutPanel容器自身可以缩放,但是里面的控件就没那么幸运了,不支持自动缩放,这样就必须写方法来触发新的事件来调整控件的大小,这样就会导致窗体的闪烁(重绘)。 借助ManagedSpy工具,我们可以看到此容器里面的器件的结构,我们可以在Form1里面添加一个事件SizeChanged 对容器里面每个器件重新给它大小 就行了。 附件:FlowLayoutPanel的Demo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值