版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一.前言
上一篇给出了流式布局,这里再来一篇瀑布流布局练手,同时代码稍微简洁一些。不再利用封装类来保存数据,直接把子 View 的四个边沿保存在对应的 LayoutParams 中。
二、分析
1.generateLayoutParams()获取封装子 View 的左上右下四个边沿的自定义 WaterfallLayoutParams。
2.onMeasure() 测量自身宽高的同时把在子 View 的 LayoutParams 存储该子 View 对应的四个边沿值。
3.onLayout()把每行 View 进行摆放。
三、实现
1.自定义的 LayoutParams
//WaterfallLayout 下的子控件的布局,保存该控件的左上右下四个边沿值
public static class WaterfallLayoutParams extends ViewGroup.LayoutParams {
public int left = 0;
public int top = 0;
public int right = 0;
public int bottom = 0;
public WaterfallLayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WaterfallLayoutParams(int width, int height) {
super(width, height);
}
public WaterfallLayoutParams(android.view.ViewGroup.LayoutParams params) {
super(params);
}
}
二、generateLayoutParams()
由于使用了自定义的 LayoutParams,这里必须全部写出以下方法,不然会报强转错误,这边只是展示有这么个用法,自定义 LayoutParams 就暂时不在这边记录。
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new WaterfallLayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new WaterfallLayoutParams(WaterfallLayoutParams.WRAP_CONTENT, WaterfallLayoutParams.WRAP_CONTENT);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new WaterfallLayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof WaterfallLayoutParams;
}
三、onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取父容器为 WaterfallLayout 设置的测量模式和大小的建议值
int iWidthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int iHeightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int iWidthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int iHeightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//记录测量的宽高
int measuredWith = 0;
int measuredHeight = 0;
//计算列的宽度
columnWidth = (iWidthSpecSize - mVerticalSpace * (columnCount - 1)) / columnCount;
//获取子 View 的数量
int childCount = getChildCount();
//如果不满一排,测量宽度为一排的实际宽度
if (childCount < columnCount) {
measuredWith = childCount * (columnWidth + mHorizontalSpace) - mHorizontalSpace;
} else {
measuredWith = iWidthSpecSize;
}
//循环遍历子 View,进行子 View 的测量以及 WaterfallLayout 宽高的计算
for (int i=0; i<childCount; i++) {
View childView = getChildAt(i);
//对当前获取的子 View 进行测量
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
//按比例计算子 View 的高度
int childViewHeight = childView.getMeasuredHeight() * columnWidth / childView.getMeasuredWidth();
//获取最小高度的列
int minHeightColumn = getMinHeightColumn();
//获取子 View 的 LayoutParams,并强转为 WaterfallLayoutParams,记录子 View 的左上右下四个边沿
WaterfallLayoutParams layoutParams = (WaterfallLayoutParams) childView.getLayoutParams();
layoutParams.left = minHeightColumn * (columnWidth + mHorizontalSpace);
layoutParams.top = columnHeight[minHeightColumn];
layoutParams.right = layoutParams.left + columnWidth;
layoutParams.bottom = layoutParams.top + childViewHeight;
//添加图片后把高度加进去
columnHeight[minHeightColumn] = layoutParams.bottom + mVerticalSpace;
}
//测量高度为最高度的列的高度
measuredHeight = columnHeight[getMaxHeightColumn()];
//测量的最终目的 setMeasuredDimension
setMeasuredDimension(iWidthSpecMode == MeasureSpec.EXACTLY ? iWidthSpecSize : measuredWith,
iHeightSpecMode == MeasureSpec.EXACTLY ? iHeightSpecSize : measuredHeight);
}
四、onLayout()
这样 onLayout() 方法就变得简洁了很多。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = this.getChildAt(i);
WaterfallLayoutParams lParams = (WaterfallLayoutParams)child.getLayoutParams();
child.layout(lParams.left, lParams.top, lParams.right, lParams.bottom);
}
//由于程序会多次调用测量布局,所以布局完清空数据
//不清空数据,同一个 View 会被多次引用布局,以最后一次布局为主,
//可能导致所有子 View 的布局都在 FlowLayout 高度之外,导致界面没有东西
clearTop();
}
五、监听
监听还是一样,没有改变
//监听类接口
public interface OnItemClickListener{
void onItemClick (View v, int index);
}
//监听类的实现
public void setOnItemClickListener(final OnItemClickListener listener){
int childCount = getChildCount();
for(int i = 0 ; i < childCount ; i++){
View childView = getChildAt(i);
final int finalI = i;
childView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
listener.onItemClick(v, finalI);
}
});
}
}
六、三个业务方法
//获取当前高度最小的列
private int getMinHeightColumn() {
int minHeightColumn = 0;
for (int i=0; i<columnCount; i++) {
if (columnHeight[i] < columnHeight[minHeightColumn]) {
minHeightColumn = i;
}
}
return minHeightColumn;
}
//获取当前高度最大的列
private int getMaxHeightColumn() {
int minHeightColumn = 0;
for (int i=0; i<columnCount; i++) {
if (columnHeight[i] > columnHeight[minHeightColumn]) {
minHeightColumn = i;
}
}
return minHeightColumn;
}
//初始化没列高度
private void clearTop() {
for (int i=0; i<columnCount; i++) {
columnHeight[i] = 0;
}
}
七、附
代码链接:http://download.csdn.net/detail/qq_18983205/9849283
这里引用 v7 包,报错的可以参考 http://blog.csdn.net/qq_18983205/article/details/60874618