自定义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)
}
效果图
现在我们要开始做换行了,思路很简单,把宽度放开,让子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)
}
效果图