Android LinearLayout实现自动换行效果
Android LinearLayout是一种常用的布局方式,能够将子View垂直或水平排列。但是,我们在实际开发中,很多时候需要实现自动换行的效果,比如说,当我们的屏幕上显示一些标签子View时,如果一行放不下了,下一个标签应该自动跳到下一行。
本文将介绍一种基于LinearLayout实现自动换行效果的方法。这里我们主要用到LinearLayout的一个属性:weightSum。首先我们先看一下如何基于LinearLayout实现标签子View的水平布局。
1. 水平布局
我们将一个标签包装成一个TextView,然后把这些TextView依次添加到LinearLayout中,设置水平排列。为了让TextView之间有一定的间隔,这里我们添加了一个0.5dp的padding。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="0.5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tag_bg"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:text="标签1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tag_bg"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:text="标签2"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tag_bg"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:text="标签3"/>
...
</LinearLayout>
布局效果如下:

我们可以看到,这里将三个标签View放在了同一行中,但我们还没有实现自动换行的效果。那么,怎样才能实现呢?接下来,我们将介绍自动换行的实现思路。
2. 自动换行
在布局中,我们可以设置LinearLayout的weightSum属性,用来平分整个布局的权值。在这个例子中,由于子View的个数是不确定的,所以我们需要计算子View总长度,并根据子View的长度平分权值。
我们定义一个类AutoLinearLayout,继承自LinearLayout。在AutoLinearLayout中,我们覆写了onMeasure()方法,以实现自动换行的功能。
public class AutoLinearLayout extends LinearLayout {
private int mTotalLength = 0;
public AutoLinearLayout(Context context) {
super(context);
}
public AutoLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public AutoLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取父布局的宽度
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
// 清空子View的权值
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
params.weight = 0;
}
// 计算所有子View的总长
mTotalLength = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
measureChild(child, widthMeasureSpec, heightMeasureSpec);
mTotalLength += child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
}
// 计算每个子View的权值
float weightSum = 0;
float weightUnit = parentWidth/mTotalLength;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
params.weight = weightUnit*(child.getMeasuredWidth() + params.leftMargin + params.rightMargin);
weightSum += params.weight;
}
// 若子View的长度超出父布局的宽度,则自动换行
if (mTotalLength > parentWidth) {
// 若只有一个子View,则此子View单独一行
if (getChildCount() == 1) {
View child = getChildAt(0);
child.layout((parentWidth - child.getMeasuredWidth())/2, 0, (parentWidth + child.getMeasuredWidth())/2, child.getMeasuredHeight());
} else {
float lineLength = 0;
int lineStart = 0;
int lineEnd = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
if (lineLength + params.weight/weightSum*(parentWidth - getPaddingLeft() - getPaddingRight()) > parentWidth - getPaddingLeft() - getPaddingRight()) {
lineEnd = i - 1;
layoutLine(lineStart, lineEnd, parentWidth, (int) (parentHeight - getPaddingTop() - getPaddingBottom() - mTotalLength/parentWidth*(child.getMeasuredHeight() + params.topMargin + params.bottomMargin)));
lineLength = 0;
lineStart = i;
}
lineLength += params.weight/weightSum*(parentWidth - getPaddingLeft() - getPaddingRight());
}
if (lineStart < getChildCount()) {
layoutLine(lineStart, getChildCount() - 1, parentWidth, (int) (parentHeight - getPaddingTop() - getPaddingBottom() - mTotalLength/parentWidth*(child.getMeasuredHeight() + params.topMargin + params.bottomMargin)));
}
}
}
}
/**
* 布局当前行
*/
private void layoutLine(int lineStart, int lineEnd, int parentWidth, int top) {
float sumWeight = 0;
for (int i = lineStart; i <= lineEnd; i++) {
View child = getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
sumWeight += params.weight;
}
float marginLeft = 0;
for (int i = lineStart; i <= lineEnd; i++) {
View child = getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
float ratio = params.weight / sumWeight;
int width = (int) (ratio * (parentWidth - getPaddingLeft() - getPaddingRight()));
int height = child.getMeasuredHeight();
int left = (int) (getPaddingLeft() + marginLeft);
int topMargin = params.topMargin;
int bottomMargin = params.bottomMargin;
int bottom = top + height;
// 布局子View
child.layout(left, topMargin + top, left + width, bottom - bottomMargin);
marginLeft += width;
}
}
}
我们在AutoLinearLayout的onMeasure()方法中,通过循环获取所有子View长度,并根据此计算每个子View的权值。若子View的总长度超出父布局的宽度,则自动换行。当只有一个子View时,此子View单独一行;当有多个子View时,循环计算每一行的总长度,当此长度超出父布局宽度时,则布局当前行,并开始下一行的计算。
通过这种方式,在布局时动态计算每个子View的权值,然后根据子View的权值来控制子View的宽度,从而实现自动换行的效果。
完成自动换行的AutoLinearLayout了之后,我们只需要在布局中将LinearLayout的声明替换成AutoLinearLayout就可以实现标签的自动换行效果了。
`
<com.example.logic.AutoLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="0.5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tag_bg"
android:paddingLeft="8dp"
