自定义 View__流式布局__FlowLayout

一、效果图

1.效果一:对应使用方式的方式一(布局中添加子 View 标签效果):
布局中添加子 View 标签效果
2.效果二:对应使用方式的方式二(代码中添加子 View 标签效果):
代码中添加子 View 标签效果

两种效果一毛一样,只是为了说明流式布局在布局中添加子 View 和在代码中添加子 View,两种实现方式都是好使的

二、使用方式

1.在布局文件中添加子 View 标签

<com.babycy.flowlayout.FlowLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Android 开发艺术探索"
            android:textAllCaps="false" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="深入理解 Java 虚拟机"
            android:textAllCaps="false" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Android 源码设计模式解析与实战"
            android:textAllCaps="false" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Java 多线程核心编程技术"
            android:textAllCaps="false" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="C++ Primer"
            android:textAllCaps="false" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="深入理解 Android"
            android:textAllCaps="false" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="设计模式之禅" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Http 权威指南"
            android:textAllCaps="false" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Android 系统源代码情景分析"
            android:textAllCaps="false" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ES6 标准入门"
            android:textAllCaps="false" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="FFmpeg 从入门到精通"
            android:textAllCaps="false" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Android 应用安全防护和逆向分析"
            android:textAllCaps="false" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="JavaScript 高级程序设计"
            android:textAllCaps="false" />

    </com.babycy.flowlayout.FlowLayout>

2.在代码中添加子 View 标签

public class AutoActivity extends AppCompatActivity {

    private FlowLayout mFlowLayout;

    private String[] mBooks = {
            "Android 开发艺术探索",
            "深入理解 Java 虚拟机",
            "Android 源码设计模式解析与实战",
            "Java 多线程核心编程技术",
            "C++ Primer",
            "深入理解 Android",
            "设计模式之禅",
            "Http 权威指南",
            "Android 系统源代码情景分析",
            "ES6 标准入门",
            "FFmpeg 从入门到精通",
            "Android 应用安全防护和逆向分析",
            "JavaScript 高级程序设计"
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_auto);

        mFlowLayout = (FlowLayout) findViewById(R.id.fl);

        initShowView();
    }

    private void initShowView() {
        LayoutInflater inflater = LayoutInflater.from(this);
        for (int i = 0; i < mBooks.length; i++) {
            TextView tv = (TextView) inflater.inflate(R.layout.tv_tag, mFlowLayout, false);
            tv.setText(mBooks[i]);
            tv.setAllCaps(false);
            mFlowLayout.addView(tv);
        }
    }
}

activity_auto.xml(FlowLayout 布局文件)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.babycy.flowlayout.FlowLayout
        android:id="@+id/fl"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

tv_tag.xml(FlowLayout 子 View 标签布局文件)

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="我是一个标签"
    android:background="@drawable/tag_bg"
    android:layout_margin="5dp"/>

tag_bg.xml(FlowLayout 子 View 标签背景,存放位置:res/drawable)

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid android:color="#e7e7e7"/>
    <corners android:radius="30dp"/>
    <padding android:left="10dp"
        android:top="5dp"
        android:right="10dp"
        android:bottom="5dp"/>

</shape>

三、实现自定义 View 流式布局

public class FlowLayout extends ViewGroup {

    public FlowLayout(Context context) {
        this(context, null);
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //流式布局 FlowLayout 宽度模式
        int wm = MeasureSpec.getMode(widthMeasureSpec);
        //流式布局 FlowLayout 宽度大小(精确值或 match_parent)
        int ws = MeasureSpec.getSize(widthMeasureSpec);
        //流式布局 FlowLayout 高度模式
        int hm = MeasureSpec.getMode(heightMeasureSpec);
        //流式布局 FlowLayout 高度大小(精确值或 match_parent)
        int hs = MeasureSpec.getSize(heightMeasureSpec);

        //流式布局 FlowLayout 宽度大小(wrap_content)
        int width = 0;
        //流式布局 FlowLayout 高度大小(wrap_content)
        int height = 0;

        //子 View 数量
        int count = getChildCount();
        //行宽
        int lineWidth = 0;
        //行高
        int lineHeight = 0;

        //遍历子 View
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            //测量子 View
            measureChild(child, widthMeasureSpec, heightMeasureSpec);

            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

            //在当前行宽 lineWidth 基础上再加当前子 View 宽度 childWidth 之后超出 FlowLayout 宽度
            if (childWidth + lineWidth > ws - getPaddingLeft() - getPaddingRight()) {
                //换行时(注意:这里只是保存换行之前的宽和高,所以当前行若为最后一行时,则可以判断在当前子 View 是最后一个子 View 时,把最后一行的行宽 lineWidth 和行高 lineHeight 更新到 FlowLayout 宽度 width 和高度 height 里)
                //每次在换行时,FlowLayout 宽度 width 取上次保存的宽度 width 和换行前一行的行宽 lineWidth 中的最大值
                width = Math.max(width, lineWidth);
                //FlowLayout 高度(height)累加行高
                height += lineHeight;

                //重置行宽为当前子 View 宽度
                lineWidth = childWidth;
                //重置行高为当前子 View 高度
                lineHeight = childHeight;
            } else {
                //没有换行时
                //这里只是记录行宽 lineWidth 和行高 lineHeight,在换行时会被更新在 FlowLayout 宽度 width 和 FlowLayout 高度 height 里
                //行宽累加子控件宽度
                lineWidth += childWidth;
                //行高取上次保存的行高和当前子 View 高度中的最大值
                lineHeight = Math.max(childHeight, lineHeight);
            }

            //当前是最后一个控件时,把最后一行的行宽 lineWidth 和行高 lineHeight 更新到 FlowLayout 宽度 width 和高度 height 里
            if (i == count - 1) {
                //FlowLayout 宽度 width 取前几行和最后一行行宽中的最大值
                width = Math.max(width, lineWidth);
                //FlowLayout 高度 height 累加行高
                height += lineHeight;
            }

            //设置 FlowLayout 宽度和高度(记得加上 padding)
            setMeasuredDimension(wm == MeasureSpec.EXACTLY ? ws : width + getPaddingLeft() + getPaddingRight(), hm == MeasureSpec.EXACTLY ? hs : height + getPaddingTop() + getPaddingBottom());
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //子 View 数量
        int count = getChildCount();
        //行宽
        int lineWidth = 0;
        //行高
        int lineHeight = 0;
        //子 View 的 top
        int top = 0;
        //子 View 的 left
        int left = 0;

        //遍历子 View
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            //当前子 View 的宽度
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            //当前子 View 的高度
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

            //换行时
            if (childWidth + lineWidth > getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) {
                //新一行的第一个子 View 的 top 累加上一行的行高
                top += lineHeight;
                //新一行的第一个子 View 的 left 重置为 0
                left = 0;

                //行宽重置为当前子 View 的宽度
                lineWidth = childWidth;
                //行高重置为当前子 View 的高度
                lineHeight = childHeight;
            } else {
                //行宽累加子 View 的宽度
                lineWidth += childWidth;
                //行高取上一次存的行高和当前子 View 宽度中的最大值
                lineHeight = Math.max(lineHeight, childHeight);
            }

            //子 View 的 left
            int cl = left + lp.leftMargin + getPaddingLeft();
            //子 View 的 top
            int ct = top + lp.topMargin + getPaddingTop();
            //子 View 的 right
            int cr = cl + child.getMeasuredWidth();
            //子 View 的 bottom
            int cb = ct + child.getMeasuredHeight();
            //根据 left、top、right、bottom 来摆放当前子 View
            child.layout(cl, ct, cr, cb);
            //当前行的当前子 View 的 left 累加上一个子 View 的宽度
            left += childWidth;
        }
    }

    /**
     * 重写 generateLayoutParams()、generateDefaultLayoutParams()
     *
     * 自定义的 ViewGroup 想要支持子控件的 layout_margin 参数,则必须重载 generateLayoutParams() 函数
     * 并且在该函数中返回一个 ViewGroup.MarginLayoutParams 派生类对象,这样才能使用 margin 参数
     *
     * 默认的 generateLayoutParams() 函数只会提取 layout_width、layout_height 的值
     * 只有 MarginLayoutParams() 才具有提取 margin 间距的功能
     */
    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

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

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

猜你喜欢

转载自blog.csdn.net/qq_21586317/article/details/80211414