自定义View - Kotlin自定义简单流式布局

319 阅读5分钟

Kotlin自定义简单流式布局

效果

class FlowLayout : ViewGroup {
	
    constructor(context: Context) : super(context) {
        init(context)
    }

    constructor(context: Context, attributeSet: AttributeSet? = null) : super(
        context,
        attributeSet
    ) {
        init(context, attributeSet)
    }

    constructor(
        context: Context,
        attributeSet: AttributeSet? = null,
        defStyle: Int = 0
    ) : super(context, attributeSet, defStyle) {
        init(context, attributeSet, defStyle)
    }

    /**
     * 横向间距
     */
    private var mHorizontalSpace = 16.dp2px()

    /**
     * 垂直间距
     */
    private var mVerticalSpace = 8.dp2px()

    /**
     * 记录所有行的所有子View
     */
    private val mAllLineViews = ArrayList<ArrayList<View>>()

    /**
     * 记录每一行的行高
     */
    private val mLineHeights = ArrayList<Int>()

    /**
     * 第一步:
     * 初始化数据
     */
    private fun init(
        context: Context,
        attributeSet: AttributeSet? = null,
        defStyle: Int = 0
    ) {

        attributeSet?.let {
            //获取属性
            var typedArray = context.obtainStyledAttributes(it, R.styleable.FlowLayout)
            //获取横向间距
            mHorizontalSpace =
                typedArray.getDimension(
                    R.styleable.FlowLayout_horizontal_space,
                    mHorizontalSpace.toFloat()
                ).toInt()
            //获取垂直间距
            mVerticalSpace =
                typedArray.getDimension(
                    R.styleable.FlowLayout_vertical_space,
                    mVerticalSpace.toFloat()
                ).toInt()
            //释放资源
            typedArray.recycle()
        }
    }

    /**
     * 可能多次测量
     * 清除所有数据
     */
    private fun initParams() {
        mLineHeights.clear()
        mAllLineViews.clear()
    }


    /**
     * 第二步: 测量
     */
    @SuppressLint("DrawAllocation")
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

        //防止内存抖动
        initParams()

        //获取FlowLayout父布局给它的宽高
        val selfWidthFromParent = MeasureSpec.getSize(widthMeasureSpec)
        val selfHeightFromParent = MeasureSpec.getSize(heightMeasureSpec)

        //测量子View
        var lineViews = ArrayList<View>() //记录每一行的所有子View
        var lineWidth = 0       //记录一行内所有的自View宽度
        var lineHeight = 0      //记录一行中最高的子View高度
        var parentWith = 0      //FlowLayout 宽度
        var parentHeight = 0    //FlowLayout 高度

        //遍历测量所有子View
        for (i in 0 until childCount) {
            //获取子View
            val childView = getChildAt(i)

            //如果子View 没有显示,则跳过此次循环
            if (childView.visibility != View.VISIBLE)
                continue

            //获取子View布局参数
            val childLp = childView.layoutParams
            //获取子View MeasureSpec
            val childWidthMeasureSpec =
                getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLp.width)
            val childHeightMeasureSpec =
                getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLp.height)
            //测量子View
            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
            //获取子View宽高
            // 在 onMeasure 方法中,所有View的宽高都只能用 view.measuredWidth 和 view.measuredHeight
            // 因为 都没测量完是不知道View的宽高的
            // 当在 onLayout 之后则就要用  view.width 和 view.height
            val childWidth = childView.measuredWidth
            val childHeight = childView.measuredHeight

            //如果行宽大于FlowLayout父布局给它的宽度,则换行
            if (lineWidth + mHorizontalSpace + childWidth > selfWidthFromParent) {
                //存储当前行的所有子View
                mAllLineViews.add(lineViews)
                //存储当前行 高
                mLineHeights.add(lineHeight)

                //创建记录下一行的所有子View 的集合
                lineViews = ArrayList<View>()
                //记得当前父布局的高度
                parentHeight += lineHeight + mVerticalSpace
                //记录当前父布局的宽度  coerceAtLeast 类似Math.max()方法
                parentWith = (lineWidth + mHorizontalSpace).coerceAtLeast(parentWith)

                //再次初始化归零
                lineWidth = 0
                lineHeight = 0
            }

            //不换行
            //当前行宽
            lineWidth += mHorizontalSpace + childWidth
            //当前行高 取最大值  coerceAtLeast 类似Math.max()方法
            lineHeight = childHeight.coerceAtLeast(lineHeight)
            //存储当前子View
            lineViews.add(childView)

            //最后一行
            if (i == childCount - 1) {
                //记录行高
                mLineHeights.add(lineHeight)
                //记录最后一行的所有的子View
                mAllLineViews.add(lineViews)

                parentHeight += lineHeight + mVerticalSpace
                parentWith = (lineWidth + mHorizontalSpace).coerceAtLeast(parentWith)
            }

        }

        //根据测量模式确定FlowLayout 真实的宽高
        val sefRealWith =
            if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) selfWidthFromParent else parentWith

        val sefRealHeight =
            if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) selfHeightFromParent else parentHeight

        //测量存储FlowLayout 宽高
        setMeasuredDimension(sefRealWith, sefRealHeight)

    }

    /**
     * 第三步:布局
     */
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {

        //起始第一个位置
        var currentLeft = paddingLeft
        var currentTop = paddingTop


        val lineSize = mAllLineViews.size
        //遍历每一行布局子View
        for (i in 0 until lineSize) {
            val lineViews = mAllLineViews[i]
            //当前行 高
            val lineHeight = mLineHeights[i]
            //遍历当前行的子View
            lineViews.forEach { childView ->
                val left = currentLeft
                val top = currentTop

                val right = left + childView.measuredWidth
                val bottom = top + childView.measuredHeight

                //布局子View
                childView.layout(left, top, right, bottom)
                //当前的宽度
                currentLeft = right + mHorizontalSpace
            }
            //当前行高
            currentTop += lineHeight + mVerticalSpace
            //换行之后重置当前宽度起点
            currentLeft = paddingLeft
        }
    }


}

fun Int.dp2px(): Int {
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        this.toFloat(),
        Resources.getSystem().displayMetrics
    ).toInt()
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:text="搜索历史"
            android:textColor="@android:color/black"
            android:textSize="18sp" />

        <com.niko.flowlayout.FlowLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="8dp"
            android:paddingLeft="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:padding="5dp"
                android:text="测试11111111" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:padding="5dp"
                android:text="测试2" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:background="@drawable/shape_button_circular"
                android:text="测试33333" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:background="@drawable/shape_button_circular"
                android:text="测试4444" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:background="@drawable/shape_button_circular"
                android:text="测试5555555" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:padding="5dp"
                android:text="测试6666666" />

            <TextView
                android:layout_width="wrap_content"
                android:padding="5dp"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试77777777" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:padding="5dp"
                android:text="测试8888888" />


            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:background="@drawable/shape_button_circular"
                android:text="测试9999999" />


            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:background="@drawable/shape_button_circular"
                android:text="测试10" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:background="@drawable/shape_button_circular"
                android:text="测试11" />
        </com.niko.flowlayout.FlowLayout>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:text="搜索发现"
            android:textColor="@android:color/black"
            android:textSize="18sp" />

        <com.niko.flowlayout.FlowLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="8dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试121212" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试1313131" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试14" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试15" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试161626" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试171717" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试181818" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试19191919" />


            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试191919" />


            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试202020" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试212121" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试22222" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试232323" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试24242424" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/shape_button_circular"
                android:text="测试252525" />
        </com.niko.flowlayout.FlowLayout>
    </LinearLayout>

</ScrollView>

attr.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="FlowLayout">
        <attr name="horizontal_space" format="dimension" />
        <attr name="vertical_space" format="dimension" />
    </declare-styleable>
</resources>