地址 FlowLayout
第一篇博客,从简单的自定义View写起吧
1.需求
与淘宝的历史搜索类似,当前行空间足够时添加到当前行,否则自动换行
支持padding和子View的margin
2.效果图
FlowLayout.jpg3.实现
1.onMeasure
<pre>
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (getChildCount() == 0) { //子view数量为0,不需要测量了
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
measureChildren(widthMeasureSpec, heightMeasureSpec);
final int childCount = getChildCount();
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
final int maxWidth = width - getPaddingEnd();
int currentWidth = getPaddingStart();
int currentTop = getPaddingTop();
View child = getChildAt(0);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
currentTop += lp.topMargin;
for (int i = 0; i < childCount; i++) {
child = getChildAt(i);
lp = (MarginLayoutParams) child.getLayoutParams();
int calcRight = (currentWidth + getViewWidth(child) + lp.leftMargin);
if (calcRight <= maxWidth) {
currentWidth = currentWidth + child.getMeasuredWidth() + lp.rightMargin;
Log.i(TAG, "onMeasure不换行: " + currentWidth);
//currentWidth = currentWidth + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
//doNothing
} else {//换行
currentTop = currentTop + lp.topMargin + lp.bottomMargin + getViewHeight(child);
currentWidth = getPaddingStart() + lp.leftMargin;
//calcRight = (currentWidth + getViewWidth(child));//currentWidth变了 需要重新算
//child.layout(currentWidth, currentTop, calcRight, currentTop + getViewHeight(child));
Log.i(TAG, "onMeasure换行: " + currentWidth);
currentWidth += child.getMeasuredWidth() + lp.rightMargin;
}
}//end for
currentTop = currentTop + child.getMeasuredHeight() + lp.bottomMargin;
setMeasuredDimension(width,heightMode == MeasureSpec.EXACTLY ? height : currentTop);
}
</pre>
首先,通过measureChildren方法通知所有子view测量自己,随后获取到系统提供给我们的宽高和测量模式。
测量过程十分简单,能摆放子view的宽度maxWidth是Flowlayout的宽度减去左右padding,所以只要用一个值记录下当前行的宽度,小于等于maxWidth就继续加,大于就归零并且换行。这里每一个子view高度margin都是相同的,所以随便拿一个当成当前行高就好了。
2.onLayout
<pre>
getViewHeight(child)就是child.getMeasureHeight</br>
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//super.onLayout();
int currentWidth = getPaddingStart();
int currentTop = getPaddingTop();
int childCount = getChildCount();
int maxWidth = getWidthWithPadding();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
child.setTag(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
currentWidth += lp.leftMargin;
//rowHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
int calcRight = (currentWidth + getViewWidth(child));
if (calcRight <= maxWidth) {
child.layout(currentWidth, currentTop, calcRight, currentTop + getViewHeight(child));
currentWidth += child.getMeasuredWidth() + lp.rightMargin;
//doNothing
} else {//需要换列
currentTop = currentTop + lp.topMargin + lp.bottomMargin + getViewHeight(child);
currentWidth = getPaddingStart() + lp.leftMargin;
calcRight = (currentWidth + getViewWidth(child));//currentWidth变了 需要重新算
if(calcRight > maxWidth)
calcRight = maxWidth;
child.layout(currentWidth, currentTop, calcRight, currentTop + getViewHeight(child));
currentWidth += child.getMeasuredWidth() + lp.rightMargin;
}
}
}
</pre>
onLayout中的代码和onMeasure十分相似,重点就在
<code>child.layout(currentWidth,currentTop,calcRight,currentTop+getViewHeight(child));
</code>
这个方法接受4个参数,分别是子view在父view中的左 上 右 下。注意的是Android中的坐标系x轴正向是右,但是y轴的正向是下。</br>
到这里 FlowLayout基本上就实现了。</br>
网友评论