來一波需求
有這樣一種需求,前面一個(gè)View,后面要帶著幾個(gè)標(biāo)簽,如果前面的View不太大,那么標(biāo)簽緊跟標(biāo)簽向前移動(dòng)(后三個(gè)條目),如果前面的View就很大,余下來足夠的空間放標(biāo)簽(第一個(gè)條目)

?酷我音樂的標(biāo)簽.png
有一個(gè)想法
自己定義一個(gè)ViewGroup,類似于水平布局的LinearLayout,優(yōu)先測量后面的View,最后將剩余的空間給第一個(gè)View,在layout的時(shí)候從左向右擺放,最終實(shí)現(xiàn)效果,給新的ViewGroup起名叫SpareLayout
- 測量的時(shí)候從最后一個(gè)子View開始測量
- 累加后面所有View的寬度,將剩余空間給第一個(gè)View
- 高度使用最大高度的View
- layout時(shí)從左向右擺放測量好的View
做一點(diǎn)實(shí)現(xiàn)
按上面的思路重寫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;
}
//測量一個(gè)子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);
}
//最后來測量第一個(gè)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);
//儲(chǔ)存測量結(jié)果
setMeasuredDimension(spareWidth + firstChild.getMeasuredWidth() + paddingWidth,
maxHeight + paddingHeight);
}
測量得到了每一個(gè)View應(yīng)該的大小,接下來就是擺放所有的子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:
//從上向下計(jì)算
topStart = lp.topMargin + getPaddingTop();
break;
case Gravity.CENTER_VERTICAL:
//vertical的居中,是指view居中(除去這個(gè)SpareLayout的padding和子View的margin居中)
topStart = (t + getPaddingTop() + lp.topMargin + //可以放view的空間上邊
b - getPaddingBottom() - lp.bottomMargin //可以放view的空間下邊
- child.getMeasuredHeight()) / 2 //中心線
- t; //計(jì)算出view的上邊
break;
case Gravity.BOTTOM:
//從下向上算的
topStart =
b - lp.bottomMargin - getPaddingBottom() - child.getMeasuredHeight() - t;
break;
default:
//默認(rèn)是在上面
topStart = lp.topMargin + getPaddingTop();
}
child.layout(leftStart, topStart, leftStart + child.getMeasuredWidth(),
topStart + child.getMeasuredHeight());
//累加左邊已經(jīng)使用的空間
left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
}
}
這樣實(shí)現(xiàn)的效果:

?文本比較長的時(shí)候.png

?文本比較短的情況.png
提一些Tips
如果后面幾個(gè)標(biāo)簽已經(jīng)很大的情況沒有處理
以前為這個(gè)效果試驗(yàn)了各種方式,跑包的時(shí)間都比停下來寫這個(gè)控件時(shí)間長得多,以后注意不要再做這樣的事
使用linearLayout的weight屬性并沒有成功