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

自定义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

        for ((index, child) in children.withIndex()) {
            var layoutParams = child.layoutParams
            var childWidthMode = 0
            var childWidthSize = 0
            when (layoutParams.width) {
                MATCH_PARENT -> {
                    when (viewGroupWidthMode) {
                        MeasureSpec.EXACTLY -> {
                            childWidthMode = MeasureSpec.EXACTLY
                            childWidthSize =
                                MeasureSpec.getSize(widthMeasureSpec) - viewGroupWidthUsed
                        }
                        MeasureSpec.AT_MOST -> {
                            childWidthMode = MeasureSpec.AT_MOST
                            childWidthSize =
                                MeasureSpec.getSize(widthMeasureSpec) - viewGroupWidthUsed
                        }
                        MeasureSpec.UNSPECIFIED -> {
                            childWidthSize = MeasureSpec.UNSPECIFIED
                            childWidthSize = 0
                        }
                    }
                }
                WRAP_CONTENT -> {
                    when (viewGroupWidthMode) {
                        MeasureSpec.EXACTLY -> {
                            childWidthMode = MeasureSpec.AT_MOST
                            childWidthSize =
                                MeasureSpec.getSize(widthMeasureSpec) - viewGroupWidthUsed
                        }
                        MeasureSpec.AT_MOST -> {
                            childWidthMode = MeasureSpec.AT_MOST
                            childWidthSize =
                                MeasureSpec.getSize(widthMeasureSpec) - viewGroupWidthUsed
                        }
                        MeasureSpec.UNSPECIFIED -> {
                            childWidthSize = MeasureSpec.UNSPECIFIED
                            childWidthSize = 0
                        }
                    }
                }
                else -> {
                    childWidthMode = MeasureSpec.EXACTLY
                    childWidthSize = layoutParams.width
                }

            }
            child.measure(
                MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode),
                MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode)
            )
            if (index >= 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) {
        **final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();**

        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)
    }

效果图

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

现在我们要开始做换行了,思路很简单,把宽度放开,让子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

                measureChildWithMargins(
                    child,
                    widthMeasureSpec,
                    0,
                    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)
            maxLineWidth = max(maxLineWidth,viewGroupWidthUsed)
        }
        viewGroupHeightUsed = maxLineHeight
        viewGroupWidthUsed = maxLineWidth
        setMeasuredDimension(viewGroupWidthUsed, viewGroupHeightUsed)
    }

效果图

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

上一篇:如何用代码实现界面ui


下一篇:自定义View讲解