前言:只要在前行,梦想就不再遥远
系列文章:
Android自定义控件三部曲文章索引:http://blog.csdn.net/harvic880925/article/details/50995268
前面两节讲解了有关ViewGroup的onMeasure、onLayout的知识,这节我们深入性地探讨一下,如何实现经常见到的瀑布流容器,本节将实现的效果图如下:
从效果图中可以看出这里要完成的几个功能:
1、图片随机添加
2、在添加图片时,总是将新图片插入到当前最短的列中
3、每个Item后,会弹出当前Item的索引
##一、初步实现WaterFallLayout
1.1 自定义控件WaterFallLayout
首先,我们自定义一个派生自ViewGroup的控件WaterFallLayout,然后再定义几个变量:
public class WaterfallLayout extends ViewGroup {
private int columns = 3;
private int hSpace = 20;
private int vSpace = 20;
private int childWidth = 0;
private int top[];
public WaterfallLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
top = new int[colums];
}
public WaterfallLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaterfallLayout(Context context) {
this(context, null);
}
…………
}
这里定义了几个变量:int columns用于指定当前的列数,这里指定的是三列;hSpace与vSpace用于指定每个图片间的水平间距和垂直间距。由于控件的宽度是一定的,当指定了列数以后,每个图片的宽度都是相同的,所以childWidth表示当前每个图片的宽度;由于每个图片的宽高比不同,所以他们的宽度相同,而高度则不同的,需要单独计算,也就没必要写成全局变量了。在开篇时,我们已经提到,我们需要把新增的图片放在容器最靠上的空白处,所以要有个top[columns]来保存当前每列的高度,以实时找到最短的高度的位置,将新增的图片放在那里。
1.2 设定onMeasure结果
通过前两篇我们知道对于ViewGroup而言onMeasure和onLayout的作用,onMeasure是告诉当前控件的父控件,它要占用的大小,以便让它的父控件给它预留。而onLayout则是布局ViewGroup中的各个元素用的。
所以首先,我们需要先计算出整个ViewGroup所要占据的大小,然后通过setMeasuredDimension()函数通知ViewGroup的父控件以预留位置,所以我们需要先求出控件所占的宽和高。
1.2.1 计算每个图片所占的宽度
所以,我们需要先求出来控件所占的宽度:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
childWidth = (sizeWidth - (columns - 1) * hSpace) / columns;
…………
}
首先,需要利用measureChildren(widthMeasureSpec, heightMeasureSpec);
让每个子控件先测量自己,只有测量过自己之后,再调用子控件的getMeasuredWidth()才会有值,所以我们在派生自ViewGroup的控件在onMeasure的时候,一般都会首先调用measureChildren()函数,以防在用到子控件的getMeasuredWidth方法的时候没值。
然后,我们需要先求个每个子控件的宽度。根据widthMeasureSpec得到的sizeWidth,是父控件建议摆放的宽度,一般也就是我们最大摆放的宽度。所以,我们根据这个宽度求出在图片摆放三列的情况下,每个控件的宽度,公式就是:
childWidth = (sizeWidth - (columns - 1) * hSpace) / columns;
由于每个图片宽度相同,而且每两个图片间是有一定间距的,距离是hSpace;在columns列的情况下,有(columns - 1)个间距,因为每两个控件的间距是hSpace,所以总的间距就是(columns - 1) * hSpace;所以计算原理就是根据总宽度减去总间距得到的就是所有子控件的总宽度和,然后除以列数,就得到了每个item的宽度。
1.2.2 求得控件总宽度
然后我们就可以根据子控件的数量是不是超过设定的列数来得到总的宽度,由于我们设定的每行的有三列,所以,如果所有子控件数并没有超过三列,那么总的控件宽度就是当前个数子控件的宽度总和组成。如果子控件数超过了三个,那说明肯定能撑满一行了,宽度也就是父控件建议的sizeWidth宽度了
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
………………
int wrapWidth;
int childCount = getChildCount();
if (childCount < columns) {
wrapWidth = childCount * childWidth + (childCount - 1) * hSpace;
} else {
wrapWidth = sizeWidth;
}
…………
}
1.2.3 求得控件总高度
在求得总宽度以后,我们要想办法得到控件的总高度,