先上完整代码:
public class TagLayout extends ViewGroup {
List childrenBounds = new ArrayList<>();
public TagLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
Rect childBound = childrenBounds.get(i);
child.layout(childBound.left, childBound.top, childBound.right, childBound.bottom);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int lineMaxWidth = 0;
int heightUse = 0;
int widthUse = 0;
int lineMaxHeight = 0;
int specMode=MeasureSpec.getMode(widthMeasureSpec);
int specWidth = MeasureSpec.getSize(widthMeasureSpec);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUse);
if (specMode!=MeasureSpec.UNSPECIFIED&&widthUse + child.getMeasuredWidth() > specWidth) {
// 另起一行
widthUse = 0;
heightUse += lineMaxHeight;
lineMaxHeight=0;
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUse);
}
Rect childBound;
if (childrenBounds.size() <= i) {
childBound = new Rect();
childrenBounds.add(childBound);
} else {
childBound = childrenBounds.get(i);
}
childBound.set(
widthUse,
heightUse,
widthUse + getMeasuredWidth(),
heightUse + getMeasuredHeight());
widthUse += child.getMeasuredWidth();
lineMaxWidth = Math.max(lineMaxWidth, widthUse);
lineMaxHeight = Math.max(lineMaxHeight, child.getMeasuredHeight());
}
int width = lineMaxWidth;
int height = lineMaxHeight+heightUse;
setMeasuredDimension(width, height);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}
## 准备工作
定义一个容器来存储子View的位置信息:
```java
List childrenBounds = new ArrayList<>();
先从onMeasure()阶段开始
定义了四个量:
int widthUse = 0;//使用了多少宽度
int heightUse = 0;//使用了多少高度
int lineMaxWidth = 0; //当前最大宽度
int lineMaxHeight = 0;//当前最大高度
这个布局是个横向排列的过程,我们用一个widthUse
来记录当前行横向使用的宽度,则计算下个子View位置时候,只需在此基础上累加即可。
而lineMaxHeight
用来记录当前行的子View中的最大高度,当换行时候传递给heightUse
,使下一阶段的子View添加下沉高度。
这段代码是什么意义:
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUse);
这其实是安卓帮我们做了一个自动计算的过程:
LayoutParams layoutParams = child.getLayoutParams();
int specWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int specWidthSize = MeasureSpec.getSize(widthMeasureSpec);
int childWidthMode;
int childWidthSize;
switch (layoutParams.width) {
case LayoutParams.MATCH_PARENT:
switch (specWidthMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
childWidthMode = MeasureSpec.EXACTLY;
childWidthSize = specWidthSize - useWidth;
break;
case MeasureSpec.UNSPECIFIED:
childWidthMode = MeasureSpec.UNSPECIFIED;
childWidthSize = 0;
break;
}
break;
case LayoutParams.WRAP_CONTENT:
break;
}
可以类比为如上的代码,根据开发者传入的参数类型进行数值的自动适配
onLayout()阶段
循环设置布局参数
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
Rect childBound = childrenBounds.get(i);
child.layout(childBound.left, childBound.top, childBound.right, childBound.bottom);
}