自定义实现FlowLayout布局
大家来看这样一个需求,你需要设计一个container,实现内部控件自动换行。即里面的控件能够根据长度来判断当前行是否容得下它,进而决定是否转到下一行显示。效果图如下:
在上图中,所有的黑色部分是FlowLayout控件,明显可以看出,内部的每个TextView控件,可以根据大小自动排列。 现在我就要通过所学的measure和layout知识自己实现一个FlowLayout。
源码如下实现如下:
public class MyFlowLayout extends ViewGroup {
public MyFlowLayout(Context context) {
super(context);
}
public MyFlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int totalWidth = 0;
int totalHeight = 0;
int lineMaxHeight = 0;
//循环遍历所有的子View并调用其measure进行自我测量
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
measureChild(child, widthMeasureSpec, heightMeasureSpec);
int childWidth = child.getMeasuredWidth() + lp.rightMargin + lp.rightMargin;
if (totalWidth + childWidth <= widthSize) {
//同一行
totalWidth += childWidth;
//统计一行中高度最大的子View
lineMaxHeight = Math.max(lineMaxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
} else {
//另起一行
totalWidth = 0;
totalHeight += lineMaxHeight;
//新的一行最大值
lineMaxHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
}
}
//退出循环后,最后一行高度没计算,手动计算
totalHeight += lineMaxHeight;
//统计所有子View的累加起来的高度作为 父View的高度
setMeasuredDimension(resolveSize(widthSize, widthMeasureSpec), resolveSize(totalHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int usedWidth = 0;
int heightOffset = 0;
int lineMaxHeight = 0;
int groupWidth = getMeasuredWidth();
//对每个一个子View进行布局
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
if (usedWidth + childWidth + lp.rightMargin + lp.leftMargin <= groupWidth) {
//没有超过一行
//摆放子View的位置
child.layout(usedWidth+lp.leftMargin, heightOffset + lp.topMargin, usedWidth+lp.leftMargin + childWidth, heightOffset + lp.topMargin + childHeight);
//统计一行中最大的高度
lineMaxHeight = Math.max(lineMaxHeight, childHeight + lp.topMargin + lp.bottomMargin);
} else {
//超过一行了,那就另起一行
heightOffset += lineMaxHeight;
child.layout(lp.leftMargin, heightOffset + lp.topMargin, lp.leftMargin + childWidth, heightOffset + lp.topMargin + childHeight);
usedWidth = 0;
//新一行第一个最大高度就是自己
lineMaxHeight = childHeight + lp.topMargin + lp.bottomMargin;
}
//已使用的宽度
usedWidth += (childWidth + lp.rightMargin + lp.leftMargin);
}
}
//------------------为了让子View支持margin属性,必须实现下面的代码------------------
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(int width, int height, int gravity) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
public LayoutParams(FrameLayout.LayoutParams source) {
super(source);
}
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MyFlowLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MyFlowLayout.LayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
return new MyFlowLayout.LayoutParams(lp);
}
}
1、由于我们的View是一个父View,所以需要继承ViewGroup类型。
2、在MyFlowLayout中分别要实现:onMeasure方法、onLayout方法、Margin属性支持。
onMeasure方法:测量每一个子View的宽高,如果子View累加起来后宽度超过父View的宽度,则重启一行,同时通过计算所有子View的尺寸推出整个父View需要的高度。
onLayout方法:为每一个子View进行布局,如果子View累加起来后的宽度超高父View的宽度,则另起一行进行布局,同时考虑margin等属性的影响。
Margin属性:如果子View要支持Margin属性,必须那父View必须实现自己的LayoutParams同时继承自MarginLayoutParams类,并通过generateDefaultLayoutParams、generateLayoutParams等方法返回自己的LayoutParams。(看上面的具体代码)
下面是使用的例子,XML布局如下:
<?xml version="1.0" encoding="utf-8"?>
<com.example.chenrongyi.myapplication.MyFlowLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#000"
android:padding="5dp"
tools:context=".MainActivity">
<Button
android:layout_width="70dp"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:text="你好吗" />
<Button
android:layout_width="80dp"
android:layout_height="70dp"
android:layout_marginRight="20dp"
android:text="你好吗" />
<Button
android:layout_width="90dp"
android:layout_height="wrap_content"
android:text="你好吗" />
<Button
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:text="你好吗" />
<Button
android:layout_width="110dp"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:text="你好吗" />
<Button
android:layout_width="70dp"
android:layout_height="40dp"
android:layout_marginRight="20dp"
android:text="哈哈" />
<Button
android:layout_width="150dp"
android:layout_height="wrap_content"
android:text="哈哈" />
</com.example.chenrongyi.myapplication.MyFlowLayout>
运行效果如下: