来一波需求
有这样一种需求,前面一个View,后面要带着几个标签,如果前面的View不太大,那么标签紧跟标签向前移动(后三个条目),如果前面的View就很大,余下来足够的空间放标签(第一个条目)
有一个想法
自己定义一个ViewGroup,类似于水平布局的LinearLayout,优先测量后面的View,最后将剩余的空间给第一个View,在layout的时候从左向右摆放,最终实现效果,给新的ViewGroup起名叫SpareLayout
- 测量的时候从最后一个子View开始测量
- 累加后面所有View的宽度,将剩余空间给第一个View
- 高度使用最大高度的View
- layout时从左向右摆放测量好的View
做一点实现
按上面的思路重写onMeasure和onLayout方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//余下的空间
int spareWidth = 0;
//最大高度
int maxHeight = 0;
//子view从后向前测量
for (int i = getChildCount() - 1; i > 0; i--) {
View child = getChildAt(i);
//不可见的跳过
if (child.getVisibility() == GONE) {
continue;
}
//测量一个子View,并处理padding,margin
measureChild(child, widthMeasureSpec, heightMeasureSpec);
FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
int marginWidth = lp.leftMargin + lp.rightMargin;
int marginHeight = lp.topMargin + lp.bottomMargin;
spareWidth += child.getMeasuredWidth() + marginWidth;
maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + marginHeight);
}
//最后来测量第一个View,使用的方式是AT_MOST,宽度是剩余空间
View firstChild = getChildAt(0);
FrameLayout.LayoutParams lp = (LayoutParams) firstChild.getLayoutParams();
int marginWidth = lp.leftMargin + lp.rightMargin;
int marginHeight = lp.topMargin + lp.bottomMargin;
int paddingWidth = getPaddingLeft() + getPaddingRight();
int paddingHeight = getPaddingTop() + getPaddingBottom();
int firstViewWidthSpec =
MeasureSpec.makeMeasureSpec(widthSize - spareWidth - marginWidth - paddingWidth,
MeasureSpec.AT_MOST);
measureChild(firstChild, firstViewWidthSpec, heightMeasureSpec);
maxHeight = Math.max(firstChild.getMeasuredHeight() + marginHeight, maxHeight);
//储存测量结果
setMeasuredDimension(spareWidth + firstChild.getMeasuredWidth() + paddingWidth,
maxHeight + paddingHeight);
}
测量得到了每一个View应该的大小,接下来就是摆放所有的子View,看过来onLayout()
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = 0;
//从左向右排放View
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
int leftStart = left + lp.leftMargin + getPaddingLeft();
int topStart;
//处理vertical的gravity
final int verticalGravity = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (verticalGravity) {
case Gravity.TOP:
//从上向下计算
topStart = lp.topMargin + getPaddingTop();
break;
case Gravity.CENTER_VERTICAL:
//vertical的居中,是指view居中(除去这个SpareLayout的padding和子View的margin居中)
topStart = (t + getPaddingTop() + lp.topMargin + //可以放view的空间上边
b - getPaddingBottom() - lp.bottomMargin //可以放view的空间下边
- child.getMeasuredHeight()) / 2 //中心线
- t; //计算出view的上边
break;
case Gravity.BOTTOM:
//从下向上算的
topStart =
b - lp.bottomMargin - getPaddingBottom() - child.getMeasuredHeight() - t;
break;
default:
//默认是在上面
topStart = lp.topMargin + getPaddingTop();
}
child.layout(leftStart, topStart, leftStart + child.getMeasuredWidth(),
topStart + child.getMeasuredHeight());
//累加左边已经使用的空间
left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
}
}
这样实现的效果:
提一些Tips
如果后面几个标签已经很大的情况没有处理
以前为这个效果试验了各种方式,跑包的时间都比停下来写这个控件时间长得多,以后注意不要再做这样的事
使用linearLayout的weight属性并没有成功