自定义实现TagLayout

175 阅读1分钟

由于需求需要,需要实现一个类似linearLayout的布局,内部可以是任意元素,当能放下就放,放不下就舍弃,能够指定行数

先上代码

class LineTagLayout(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
        ViewGroup(context, attrs, defStyleAttr) {

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context) : this(context, null)

    var horizontalGap = dp2px(4f)
    var verticalGap = dp2px(4f)
    var maxLines = 1

    val mViews = mutableListOf<List<View>>()
    val mHeights = mutableListOf<Int>()


    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        mViews.clear()
        mHeights.clear()

        val selfWidth = MeasureSpec.getSize(widthMeasureSpec)
        val selfHeight = MeasureSpec.getSize(heightMeasureSpec)

        var lineVies = mutableListOf<View>()
        var widthUsed = 0
        var lineHeight = 0
        var maxWidth = 0
        var maxHeight = 0

        for (i in 0 until childCount) {
            val child = getChildAt(i)
            if (child.visibility == View.GONE) {
                continue
            }
            val lp = child.layoutParams
            val childWidthSpec =
                    getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, lp.width)
            val childHeightSpec =
                    getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, lp.height)
            child.measure(childWidthSpec, childHeightSpec)
            val childWidth = child.measuredWidth
            val childHeight = child.measuredHeight

            var addedWidth = widthUsed + childWidth + if (lineVies.size > 0) horizontalGap else 0
            if (addedWidth > selfWidth) {
                //换行
                mViews.add(lineVies)
                mHeights.add(lineHeight)
                maxHeight += lineHeight
                maxWidth = max(maxWidth, widthUsed)
                //清理数据
                lineHeight = 0
                widthUsed = 0
                lineVies = mutableListOf()
                addedWidth = childWidth

                if (mViews.size >= maxLines) {
                    //舍弃
                    break
                }
            }
            lineVies.add(child)
            lineHeight = max(lineHeight, childHeight)
            widthUsed = addedWidth
        }
        if (lineVies.size > 0) {
            mViews.add(lineVies)
        }
        if (lineHeight > 0) {
            mHeights.add(lineHeight)
        }
        maxHeight += lineHeight + (mViews.size - 1) * verticalGap
        maxWidth = max(maxWidth, widthUsed)

        val realWidth =
                if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) selfWidth else maxWidth
        val realHeight =
                if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) selfHeight else maxHeight
        setMeasuredDimension(realWidth, realHeight)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        val rtl = layoutDirection == LAYOUT_DIRECTION_RTL

        var lineBottom = paddingTop
        for ((row, line) in mViews.withIndex()) {
            var start = if (rtl) r - l - paddingStart else paddingStart
            lineBottom += mHeights[row]
            if (row > 0) {
                lineBottom += verticalGap
            }
            for ((index, item) in line.withIndex()) {
                if (index > 0) {
                    start = if (rtl) start - horizontalGap else start + horizontalGap
                }
                val top = lineBottom - item.measuredHeight
                val left = if (rtl) start - item.measuredWidth else start
                val right = if (rtl) start else start + item.measuredWidth
                val bottom = lineBottom
                item.layout(left, top, right, bottom)
                start = if (rtl) left else right
            }
        }
    }


    private fun dp2px(dp: Float): Int {
        return TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                dp,
                Resources.getSystem().displayMetrics
        ).toInt()
    }
}

其实代码比较简单,有一些需要注意的点就是需要考虑父容器的padding,这里没有考虑子元素的margin,因为设置的是间距。然后还考虑了rtl。