Android 自定义流布局。使用开源库SimpleFlowLa
2016-06-23 本文已影响479人
张云飞Vir
前言
实际项目中需要实现一个 热门搜索 的栏目,类似下图:
由于 子项(子view) 中的文字是可变的,一行能显示的 子项 的个数也无法确定。需要支持自动换行和计算位置。
�# 使用开源类库SimpleFlowLayout
我自己写了个 自定义view ,继承自viewGroup, 来实现它,托管到github开源平台。
名称:SimpleFlowLayout
地址:https://github.com/vir56k/SimpleFlowLayout
特点:可以不断添加多个子view,计算位置,自动换行。 类似html中的div标签
适用: 热门标签
实现思路
要实现 自定义的viewgroup,需要:
- 继承自 ViewGroup
- 实现 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
这个方法用于测量 自己(自定义view)本身需要的宽度和高度 - 实现 protected void onLayout(boolean changed, int l, int t, int r, int b)
这个方法用于指定如何摆放 子view 的位置。
实现代码
package zhangyf.vir56k.flowframelayout;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* name: android 简单的流布局自定义view
* 作者:张云飞vir
* 特点:可以不断添加多个子view,计算位置,自动换行。
* 适用: 热门标签
* Created by zhangyunfei on 15/12/4.
*/
public class SimpleFlowLayout extends ViewGroup {
public SimpleFlowLayout(Context context) {
super(context);
}
public SimpleFlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SimpleFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMax = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMax = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthNeed = 0;
int heightNeed = 0;
int x = 0;
int y = 0;
int currentLineHeight = 0;
View child;
for (int i = 0; i < getChildCount(); i++) {
child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
child.measure(widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//获得子view的 外边距
//测算子view宽度,本行这句代码有问题,不能计算子view的自动换行 int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
//使用viewGroup的measureChildWithMargins测算宽度,在这个方法里处理了 LayoutParams的match_parent等方式的处理
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
if (x + childWidth > widthMax) {//换行处理,本行高度和x轴都清零,y轴下移(加上上次的行高)
y += currentLineHeight;
currentLineHeight = 0;
x = 0;
}
x += childWidth;
currentLineHeight = Math.max(currentLineHeight, childHeight);
widthNeed = Math.max(widthNeed, x);//加入了这个 子view后,留下最大宽度
heightNeed = Math.max(heightNeed, y + currentLineHeight);//对比上次的,留下最大的高度
}
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthMax : widthNeed,
heightMode == MeasureSpec.EXACTLY ? heightMax : heightNeed);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int widthMax = getWidth();
int x, y;
x = 0;
y = 0;
View child;
int left = 0;
int top = 0;
int currentLineHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (x + childWidth > widthMax) {//换行处理
y += currentLineHeight;
x = 0;
currentLineHeight = 0;
}
left = x + lp.leftMargin;
top = y + lp.topMargin;
//定位子view的位置
child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
x += childWidth;
currentLineHeight = Math.max(currentLineHeight, childHeight);
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}