自适应布局FlowLayout

这是我的第一篇文章,想了很久不知道写什么内容,估计目前也没有什么能力写深奥的,那就写写之前写过的自定义view,分享一下,有不正确的地方往指正,大家共同学习。

好了,正文来了,这篇是主要写自适应布局,也就是添加的view从左到右排好,若新一个view在这一行放不下就放在下一行。

自定义view第一步是在attr.xml写属性,不过FlowLayout比较简单没有自定义属性,直接跳到后面的测量,布局等,那么就先重写onMeasure()方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //每行的的宽度
        int width = getPaddingStart() + getPaddingEnd();
        //自适应的长度
        int height = getPaddingTop() + getPaddingBottom();
        //最大宽度
        int maxWidth = 0;
        //每一行的最大长度
        int maxHeight = 0;
        //遍历子view
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            //测量子view
            measureChild(view, widthMeasureSpec, heightMeasureSpec);
            //获取子view的外边距
            MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
            //子view的宽,要加上子view的外边距,不然margin属性设置了没效果
            int w = view.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
            //子view的高
            int h = view.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
            //判断是否是该行最大长度
            maxHeight = maxHeight > h ? maxHeight : h;
            //判断加入该view后是否超过测量宽度,超过的话就换行
            if (measureWidth < w + width) {
            	//判断该view是否超过父容器的最大值,是的话,设该view的宽度为父容器的测量值
                if (w > measureWidth) {
                    w = measureWidth;
                }
                //换行,新一行的宽度重置,最大长度增加
                width = getPaddingStart() + getPaddingEnd();
                height += maxHeight;
            }
            //该行加入子view宽度
            width += w;
            //获取最大宽度,其实最大也就是父容器的测量值
            maxWidth = maxWidth > width ? maxWidth : width;
            //因为是从0开始的,所以最后一个view时需要加上当前这一行的高度
            if (i == getChildCount() - 1) {
                height += maxHeight;
            }
        }
        if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
            setMeasuredDimension(measureWidth, measureHeight);
        } else if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(measureWidth, height);
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.EXACTLY) {
            setMeasuredDimension(maxWidth, measureHeight);
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(maxWidth, height);
        } else {
            setMeasuredDimension(measureWidth, height);
        }
    }

以上就是重写后的onMeasure()方法,看注释应该就懂了,不过有一点需要注意,就是MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();这段,获取子view的外边距,这就要重写generateLayoutParams()和generateDefaultLayoutParams()这两个方法了

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

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

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

因为MarginLayoutParams时继承LayoutParams的,而且有外边距属性,所以将LayoutParams转化为MarginLayoutParams就好了,这就可以拿到子view的外边距属性了。
那么测量完之后呢,那就是布局了,布局就时重写onLayout()方法

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    	//该行的开始宽度,是要加上父容器的内边距
        int width = getPaddingStart();
        //这是总高度
        int height = getPaddingTop();
        //这是目标行的最大高度
        int maxHeight = 0;
        //目标行的最大宽度
        int maxWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd();
        //遍历子view
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
            //子view宽度
            int w = view.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
            //子view高度
            int h = view.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
            maxHeight = maxHeight > h ? maxHeight : h;
            if (maxWidth < w + width) {
                if (w > maxWidth) {
                    w = maxWidth;
                }
                //换行,重置该行的开始宽度,目标行的高度增加
                width = getPaddingStart();
                height += maxHeight;
                maxHeight = 0;
            }
            width += w;
            //子view的左上角的坐标,width已经加上子view的宽度了,只要减去w就是开始坐标,加上子view的左外边距就可以了
            int viewL = width - w + layoutParams.leftMargin;
            int viewR = width - layoutParams.rightMargin;
            int viewT = height + layoutParams.topMargin;
            int viewB = ((height + h) > getMeasuredHeight() ? getMeasuredHeight() : (h + height)) - layoutParams.bottomMargin;
            view.layout(viewL, viewT, viewR, viewB);
        }
    }

以上就是onLayout()方法,其实跟onMeasure()方法差不多,那么我们来看看效果吧,首先添加到activity的布局里

<android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.project.viewtest.activity.FlowActivity">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="add"
            android:id="@+id/flow_add"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="back"
            android:id="@+id/flow_back"
            app:layout_constraintLeft_toRightOf="@id/flow_add"/>

        <com.project.viewtest.widget.FlowLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/flow_layout"
            app:layout_constraintTop_toBottomOf="@id/flow_add"/>

    </android.support.constraint.ConstraintLayout>

从布局可以看出,有一个添加按钮,就是给FlowLayout添加子view的,那么看一下activity的内容

public class FlowActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flow);
        final FlowLayout layout = findViewById(R.id.flow_layout);
        //返回按钮
        findViewById(R.id.flow_back).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
        //添加按钮
        findViewById(R.id.flow_add).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TextView textView = new TextView(FlowActivity.this);
                textView.setText(getText());
                textView.setBackgroundResource(R.drawable.text_bg);
                ViewGroup.MarginLayoutParams layoutParams = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                //设置外边距
                layoutParams.setMargins(15, 15, 0, 0);
                textView.setLayoutParams(layoutParams);
                //添加
                layout.addView(textView);
            }
        });
    }

	//获取随机字符串
    private String getText() {
        int count = (int) ((Math.random() + Math.random()) * 10);
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < count; i++) {
            builder.append((char) ((int) (Math.random() * 93) + 32));
        }
        Log.i("flowActivity", "getText: " + count + "/" + builder.toString());
        return builder.toString();
    }
}

现在可以看效果了
初始状态
添加view后的
添加view后的状态
这样就写好一个自适应FlowLayout了,这是我在启舰大神的博客看到的,不过我没看代码,就是想自己写一个,附上启舰大神的博客:https://blog.csdn.net/harvic880925?t=1 ,可以去看看,对比一下。
有哪里不懂的可以提问,有哪里不对的可以指正,谢谢。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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、付费专栏及课程。

余额充值