自定义viewGroup测量以及子view布局

通常上,自定义viewGroup需要给子view进行测量,布局两个步骤,今天我们看看简单的自定义标签布局应该怎么实现

假如我以及子view全部测量好了,那我只要在onlayout里面

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        for ((index, child) in children.withIndex()) {
            child.layout(list[index].left, list[index].top, list[index].right, list[index].bottom)
        }
    }

美滋滋,把坐标全部填入就行了,那子view应该怎么测量呢

我们需要考虑两个因素,一个是开发者对Taglayout的要求,一个是开发者对子view的要求,以测量子view的宽度为例子

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val viewGroupWidthSize = MeasureSpec.getSize(widthMeasureSpec)
        val viewGroupWidthMode = MeasureSpec.getMode(widthMeasureSpec)
        var viewGroupWidthUsed = 0
        var viewGroupHeightUsed = 0
<pre><code>    for ((index, child) in children.withIndex()) {
        var layoutParams = child.layoutParams
        var childWidthMode = 0
        var childWidthSize = 0
        when (layoutParams.width) {
            MATCH_PARENT -&gt; {
                when (viewGroupWidthMode) {
                    MeasureSpec.EXACTLY -&gt; {
                        childWidthMode = MeasureSpec.EXACTLY
                        childWidthSize =
                            MeasureSpec.getSize(widthMeasureSpec) - viewGroupWidthUsed
                    }
                    MeasureSpec.AT_MOST -&gt; {
                        childWidthMode = MeasureSpec.AT_MOST
                        childWidthSize =
                            MeasureSpec.getSize(widthMeasureSpec) - viewGroupWidthUsed
                    }
                    MeasureSpec.UNSPECIFIED -&gt; {
                        childWidthSize = MeasureSpec.UNSPECIFIED
                        childWidthSize = 0
                    }
                }
            }
            WRAP_CONTENT -&gt; {
                when (viewGroupWidthMode) {
                    MeasureSpec.EXACTLY -&gt; {
                        childWidthMode = MeasureSpec.AT_MOST
                        childWidthSize =
                            MeasureSpec.getSize(widthMeasureSpec) - viewGroupWidthUsed
                    }
                    MeasureSpec.AT_MOST -&gt; {
                        childWidthMode = MeasureSpec.AT_MOST
                        childWidthSize =
                            MeasureSpec.getSize(widthMeasureSpec) - viewGroupWidthUsed
                    }
                    MeasureSpec.UNSPECIFIED -&gt; {
                        childWidthSize = MeasureSpec.UNSPECIFIED
                        childWidthSize = 0
                    }
                }
            }
            else -&gt; {
                childWidthMode = MeasureSpec.EXACTLY
                childWidthSize = layoutParams.width
            }

        }
        child.measure(
            MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode),
            MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode)
        )
        if (index &gt;= list.size) {
            list.add(Rect())
        }
        list[index].set(
            viewGroupWidthUsed,
            viewGroupHeightUsed,
            viewGroupWidthUsed + child.measuredWidth,
            viewGroupHeightUsed + child.measuredHeight
        )

    }

我们就是这样测量宽度的,父view是精确值,子view是填满,所以子view的宽度就确定了

父view是限制大小模式,子view是填满,那子view是精确值模式,值是父view被限制的大小,其实这一块代码应该抽出来,如果没有什么特俗要求,我们可以使用Google帮我们封装好的方法

measureChildWithMargins(child,widthMeasureSpec,viewGroupWidthUsed,heightMeasureSpec,viewGroupHeightUsed)
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
<strong>final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();</strong></p>
<pre><code>    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

因为measureChildWithMargins里面用的MarginLayoutParams所以我们需要在我们TagLayout里面重写getLayoutParams

override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context, attrs)
}

重构后的onMeasure

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var viewGroupWidthUsed = 0
var viewGroupHeightUsed = 0
var maxLineHeight = 0
for ((index, child) in children.withIndex()) {
measureChildWithMargins(
child,
widthMeasureSpec,
viewGroupWidthUsed,
heightMeasureSpec,
viewGroupHeightUsed
)
if (index >= list.size) {
list.add(Rect())
}
list[index].set(
viewGroupWidthUsed,
viewGroupHeightUsed,
viewGroupWidthUsed + child.measuredWidth,
viewGroupHeightUsed + child.measuredHeight
)
viewGroupWidthUsed += child.measuredWidth
maxLineHeight =  max(maxLineHeight, child.measuredHeight)
}
viewGroupHeightUsed = maxLineHeight
setMeasuredDimension(viewGroupWidthUsed, viewGroupHeightUsed)
}

效果图

!https://s3-us-west-2.amazonaws.com/secure.notion-static.com/0b5a5739-d114-49e4-864b-86df63e8d8a2/Untitled.png

现在我们要开始做换行了,思路很简单,把宽度放开,让子view随便测,如果子view宽度+已用的宽度,大于父view宽度,则刷新可用高度,可用宽度,再次给子view测一次高宽

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var viewGroupWidthUsed = 0
var viewGroupHeightUsed = 0
var maxLineHeight = 0
var maxLineWidth = 0
for ((index, child) in children.withIndex()) {
measureChildWithMargins(
child,
widthMeasureSpec,
0,
heightMeasureSpec,
viewGroupHeightUsed
)
if (viewGroupWidthUsed + child.measuredWidth > MeasureSpec.getSize(widthMeasureSpec)) {
viewGroupWidthUsed = 0
viewGroupHeightUsed = maxLineHeight</p>
<pre><code>            measureChildWithMargins(
                child,
                widthMeasureSpec,
                0,
                heightMeasureSpec,
                viewGroupHeightUsed
            )
        }

        if (index &gt;= list.size) {
            list.add(Rect())
        }
        list[index].set(
            viewGroupWidthUsed,
            viewGroupHeightUsed,
            viewGroupWidthUsed + child.measuredWidth,
            viewGroupHeightUsed + child.measuredHeight
        )
        viewGroupWidthUsed += child.measuredWidth
        maxLineHeight =  max(maxLineHeight, child.measuredHeight + viewGroupHeightUsed)
        maxLineWidth = max(maxLineWidth,viewGroupWidthUsed)
    }
    viewGroupHeightUsed = maxLineHeight
    viewGroupWidthUsed = maxLineWidth
    setMeasuredDimension(viewGroupWidthUsed, viewGroupHeightUsed)
}

效果图