自定义一个标签自动排版显示的布局。效果:
思路 一、:确定布局的宽,高。
宽度确定:所有行中,宽度最宽的行即为这个布局的宽。
高度确定 :所有行的高度加起来 即 为这个布局的高度。
二、摆放子View思路
如果能够确定每一个Child的 right 和 bottom ,或者 left,top,那么就可以遍历每一个child, 直接摆放即可。
那么就围绕这个思路 来计算每一个child的right和bottom.
这里我用ArrayList<int[]> 数据格式 来存储每一个child的right和bottom。int[] 数组里面存放着right ([o])和bottom( [1] ).
三、确定换行时机 和 每一个child的right和bottom 。
遍历每一个child,获取child的measureWidth 和marginLeft marginRight,并且累加求和,注意,这里的累加求和就是每一个child的right 了。
当:
(tempLineWidth + currentChildWidth) > parentWidth 成立时 即表示该换行了。当换行时 就可以确定这一行所有child的bottom或者top了。
代码:onMeasure
/**
* 父容器体提供的宽高大小规格
*
* @param widthMeasureSpec 最大宽度(不能超过)
* @param heightMeasureSpec 最大高度
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//根据父容器给定的规格和工具类, 得到每一个子view 想要的的大小。
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 注意这里的margin 属性不能用来计算宽高,因为属于位置概念 而不是大小概念。
int childWantWidth = child.getLayoutParams().width;//+ lp.leftMargin + lp.rightMargin;
int childWantHeight = child.getLayoutParams().height;//+ lp.topMargin + lp.bottomMargin;
//注意 ,child想要的宽高 不包括 margin属性。所以这里不需要 加上 margin属性。
int childMeasuredWidth = getChildMeasureSpec(widthMeasureSpec, 0, childWantWidth);
int childMeasureHeight = getChildMeasureSpec(heightMeasureSpec, 0, childWantHeight);
// 子view 自己测量 大小
child.measure(childMeasuredWidth, childMeasureHeight);
// measureChild(child, widthMeasureSpec, heightMeasureSpec);
// measureChildWithMargins(child,widthMeasureSpec,0,heightMeasureSpec,0);
}
//测量viewgroup自己
//根据父容器给定的规格(widthMeasureSpec,heightMeasureSpec)拿到自己的规格
int widthMode = MeasureSpec.getMode(widthMeasureSpec); //mode
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int atMostWidth = MeasureSpec.getSize(widthMeasureSpec); //value
int atMostHeight = MeasureSpec.getSize(heightMeasureSpec);
//根据拿到的规格计算出各种mode下的测量值。
int viewGroupMeasuredWidth = 0;
int viewGroupMeasuredHeight = 0;
//每一行的高度
int lineHeight = 0;
int lineWidth = 0;
// 遍历子view 同时测量viewgroup的宽高。
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 获取当前行高度
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if ((lineWidth + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin) > atMostWidth) {
//需要换行显示
viewGroupMeasuredWidth = atMostWidth;//Math.max(atMostWidth, viewGroupMeasuredWidth);
//重新下一行
lineWidth = 0;
lineWidth += child.getMeasuredWidth();
//累加当前行高度。
viewGroupMeasuredHeight += lineHeight;
lineHeight = 0;
lineHeight = Math.max(lineHeight, childHeight);
} else {
// 累加 子view的宽度
lineWidth += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight);
}
//在最后一个child确定 viewgroup 大小
if (i == childCount - 1) {
//? 如果最后一个控件宽度很大 怎么办,在最后取最大值。否则在循环中取累加值
//测试 一个宽度很大的view 600dp
viewGroupMeasuredWidth = Math.max(viewGroupMeasuredWidth, atMostWidth);
lineHeight = Math.max(lineHeight, childHeight);
viewGroupMeasuredHeight += lineHeight;
}
}
if (widthMode == MeasureSpec.EXACTLY) {
viewGroupMeasuredWidth = atMostWidth;
}
if (heightMode == MeasureSpec.EXACTLY) {
viewGroupMeasuredHeight = atMostHeight;
}
setMeasuredDimension(viewGroupMeasuredWidth, viewGroupMeasuredHeight);
}
摆放的 代码:onLayout
/**
* //摆放 采用(left,top) 和(right,bottom)这两个点来固定子view的位置
*
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//摆放 采用(left,top) 和(right,bottom)这两个点来固定子view的位置
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
int childCount = getChildCount();
int parentWidth = getMeasuredWidth();
ArrayList<Integer> lineViewRight = new ArrayList<>(); //保存所有view的right坐标
//保存所有view的right和bottom。
// right:(marginLeft+measuredWidth+marginRight),
// bottom:(marginTop+ measuredHeight+marginBottom)
ArrayList<int[]> wh = new ArrayList();
int tempLineWidth = 0; //当前行view的累加宽度(也就是right)
int tempLineHeight = 0; //当前行最高高度
int newLineTag = 0; //换行标记
int fianlHeight = 0; //当前view的bottom
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int currentChildWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int currentChildHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
// 核心计算 代码
if ((tempLineWidth + currentChildWidth) > parentWidth) { //换行
//保存上一行所有view的right和bottom
fianlHeight += tempLineHeight;
for (int j = 0; j < newLineTag; j++) {
int finalR = lineViewRight.get(j);
int[] rightHeight = {finalR, fianlHeight};
wh.add(rightHeight);
}
lineViewRight.clear();
newLineTag = 0;
tempLineHeight = 0;
//宽
tempLineWidth = 0;
tempLineWidth += currentChildWidth;
lineViewRight.add(currentChildWidth);
newLineTag++;
//高
tempLineHeight = Math.max(tempLineHeight, currentChildHeight);
} else {
newLineTag++;
//宽 继续追加排列
tempLineWidth += currentChildWidth;
lineViewRight.add(tempLineWidth); //记录每一view的right。
//高
tempLineHeight = Math.max(tempLineHeight, currentChildHeight);
}
//最后一行
if (i == childCount - 1) {
fianlHeight += tempLineHeight;
for (int j = 0; j < lineViewRight.size(); j++) {
int finalR = lineViewRight.get(j);
int[] rightHeight = {finalR, fianlHeight};
wh.add(rightHeight);
}
}
}
System.out.println("childcount:" + childCount + "--wh:" + wh.size());
if (childCount != wh.size()) {
throw new IllegalArgumentException("layout 摆放计算失败。");
}
int len = wh.size();
for (int i = 0; i < len; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int[] widthHeight = wh.get(i);
int childRight = widthHeight[0];
int childBottom = widthHeight[1];
left = childRight - child.getMeasuredWidth() - lp.rightMargin;
top = childBottom - child.getMeasuredHeight() - lp.bottomMargin;
right = childRight - lp.rightMargin;//或者left+child.getMeasuredWidth();
bottom = childBottom - lp.bottomMargin;
child.layout(left, top, right, bottom);
}
}
/**
* 如果自定义viewgroup 在测量时需要margin 属性用来计算的,复写父类此方法。
* 返回MarginLayoutParams 即可。
*
* @param attrs
* @return
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
使用:
<example.nzh.com.beisaier.view.MyView2
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:background="#a8a8a8">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/bg_week_nomal"
android:text="方法" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/bg_week_nomal"
android:text="苹果" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/bg_week_nomal"
android:text="西红柿" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/bg_week_nomal"
android:text="香蕉苦瓜" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/bg_week_nomal"
android:text="方法方法方法方法方法" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/bg_week_nomal"
android:text="方法方法" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/bg_week_nomal"
android:text="方法方法" />
</example.nzh.com.beisaier.view.MyView2>