自适应布局FlowLayout

作者: 仕囵弹 | 来源:发表于2019-03-25 12:12 被阅读1次

    这是我的第一篇文章,想了很久不知道写什么内容,估计目前也没有什么能力写深奥的,那就写写之前写过的自定义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后的

    这样就写好一个自适应FlowLayout了,这是我在启舰大神的博客看到的,不过我没看代码,就是想自己写一个,附上启舰大神的博客:https://blog.csdn.net/harvic880925?t=1 ,可以去看看,对比一下。

    有哪里不懂的可以提问,有哪里不对的可以指正,谢谢。

    相关文章

      网友评论

        本文标题:自适应布局FlowLayout

        本文链接:https://www.haomeiwen.com/subject/qwjrnxtx.html