您的位置:首页 > 其它

自定义流式布局FlowLayout

2016-09-17 14:26 204 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/huanglq_ok/article/details/52563058

描述:
每一行摆放多个子View时,因为每个View的宽度和高度可能不同,需要在摆放之时按实际剩余宽度布置子View的摆放,如果当前行不够空间摆放,则需要新创建一行摆放它,而父容器FlowLayout的高度需要计算出所有行Line的高度之和来确定.
效果图:

实现要点:

  1. onMeasure()方法中计算出每一行的最大可用宽度
//1,计算宽高
int width = MeasureSpec.getSize(widthMeasureSpec);
int maxWidth = width - getPaddingLeft() - getPaddingRight();//最大可用宽度

2, 定义个管理类Line: a,用于管理新进来的子View是否可添加进当前行canAddView(),如果可以添加,则添加进集合addView(),同时记录已使用了的宽度usedWidth,及当前最高的子View的高度mHeight(用于确定该行Line的高度); b,布局最终添加进集合的每一个子View在当前行的摆放位置onLayout();

public class Line {
private int usedWidth;
private int paddingWidth;
private int mMaxWidth;
private List<View> mViews = new ArrayList<>();
private int mHeight;
public Line(int paddingWidth, int maxWidth) {
this.paddingWidth = paddingWidth;
mMaxWidth = maxWidth;
}
public boolean canAddView(View child) {
if (mViews.size() == 0) {
return true;
}
return usedWidth + child.getMeasuredWidth() + paddingWidth <= mMaxWidth;
}
public void addView(View child) {
int childWidth = child.getMeasuredWidth();
if (mViews.size() > 0) {
usedWidth += paddingWidth;    //当添加进一个子View后,就需要加上每个子View间的间距了
}
mViews.add(child);
usedWidth += childWidth;
mHeight = mHeight > child.getMeasuredHeight() ? mHeight : child.getMeasuredHeight();    //用于确定每一行的高度
}
.......

}

3, 创建成员变量mCurrentLine记录当前行Line的引用(相当于指针),及集合mLines用于存储每一个new出来的Line, 在onMeasure()方法中遍历每一个孩子,循环判断mCurrentLine是否为空,如果为空就创建Line,并addView(child),如果不为空,则分两种情况判断:mCurrentLine.canAddView(child)返回为true,则直接添加;返回为false,需要重新 new Line(),同时当前行的引用mCurrentLine指向新创建的Line,再addView(child),同时添加进集合mLines中 , 每次添加完成后,再测量一下孩子,给孩子的宽高赋值:

//3,测量孩子,并添加到Line中
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
//将孩子添加到Line中
if (mCurrentLine == null) {
mCurrentLine = new Line(getResources().getDimensionPixelSize(R.dimen.flow_line_padding), maxWidth);
mCurrentLine.addView(child);
mLines.add(mCurrentLine);
} else {
if (mCurrentLine.canAddView(child)) {
mCurrentLine.addView(child);
} else {
mCurrentLine = new Line(getResources().getDimensionPixelSize(R.dimen.flow_line_padding), maxWidth);
mCurrentLine.addView(child);
mLines.add(mCurrentLine);
}
}
//测量孩子
measureChild(child, widthMeasureSpec, heightMeasureSpec);    //对孩子的宽高不做限制,
}

4, 测量自己
宽度使用期望的宽度width, 高度需要遍历mLines集合,累加每一Line的mHeight:

//4,测量自己
int measuredHeight = getPaddingTop() + getPaddingBottom();//将高度的内边距计算在内
for (int i = 0; i < mLines.size(); i++) {
measuredHeight += mLines.get(i).mHeight + mVerticalSpace;    //将每一行的垂直间距计算在内
}
setMeasuredDimension(width, measuredHeight);

5, 根据 left 和 top 布局每一行,及每一行的中的孩子的位置:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int top = t + getPaddingTop();    //将内边距计算在内
int left = l + getPaddingLeft();
for (int i = 0; i < mLines.size(); i++) {
Line line = mLines.get(i);
line.onLayout(left, top);
top += line.mHeight + mVerticalSpace;    //每循环一次将top增加line.mHeight 及 每行的垂直间距 的高度
}
}
Line类中的onLayout,需要根据已使用的宽度usedWidth计算出最终剩余使用不了的宽度, 将这部分宽度按照mViews.size()的个数(即每一行的孩子个数)平均分配到每一个child的宽度上去,调用child.measure()方法按照指定的宽度重新测量后,再对孩子布局位置child.layout();
public void onLayout(int l, int t) {
int left = l;
for (int i = 0; i < mViews.size(); i++) {
View child = mViews.get(i);
//计算出平均添加到每个child上的多余宽度
int avg = (mMaxWidth - usedWidth) / mViews.size();
//按照指定的值重新测量孩子
child.measure(MeasureSpec.makeMeasureSpec(child.getMeasuredWidth() + avg,MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),MeasureSpec.EXACTLY));
int right = left + child.getMeasuredWidth();
int bottom = t + child.getMeasuredHeight();
child.layout(left, t, right, bottom);
left = right + paddingWidth;
}
}

6, 因为onMeasure()方法会被重复调用多次, 所以需要在onMeasure()方法每次被调用之前将集合清空, 不然会出现大片空白的区域:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//因为会多次调用measure()方法,所以方法开始前先清除child
mLines.clear();
mCurrentLine = null;
.......
}

效果图实现代码

FlowLayout flowLayout = (FlowLayout) findViewById(R.id.flowlayout);
mRandom = new Random();
for (int i = 0; i < mDatas.length; i++) {
TextView view = new TextView(this);
view.setText(mDatas[i]);
//创建正常情况下背景
GradientDrawable gradientDrawable = new GradientDrawable();
gradientDrawable.setCornerRadius(getResources().getDimensionPixelSize(R.dimen.flow_gradient_radius));
int a = 255;
int r = 150 + mRandom.nextInt(100);
int g= 150 + mRandom.nextInt(100);
int b= 150 + mRandom.nextInt(100);
gradientDrawable.setColor(Color.argb(a, r, g, b));
//创建被点击时的背景
GradientDrawable gradientDrawable2 = new GradientDrawable();
gradientDrawable2.setCornerRadius(getResources().getDimensionPixelSize(R.dimen.flow_gradient_radius));
gradientDrawable2.setColor(Color.GRAY);
//设置图片选择器
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(new int[]{android.R.attr.state_pressed},gradientDrawable2);
stateListDrawable.addState(new int[]{},gradientDrawable);

view .setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, ((TextView) v).getText(), Toast.LENGTH_SHORT).show();
}
});
//将背景设置到TextView中
view.setBackgroundDrawable(stateListDrawable);
view.setTextColor(Color.WHITE);
view.setPadding(5, 5, 5, 5);
view.setGravity(Gravity.CENTER);
view.setTextSize(14);

flowLayout.addView(view);//触发重绘
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: