自适应布局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后的

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容