前言
在博主的一个小项目中,需要实现动态列表中的条目有显示多张图片的功能,目前在demo中的效果是下面这样子的
可以看到上面的九宫格的控件显示的效果是蛮好的,图片的个数不同,显示的效果就不同.那么博主就带大家做一下下啦
明确需求
首先明确这是一个有孩子的控件,并且此控件只是对孩子的位置进行了放置,本身并没有什么显示效果
那么这就需要我们去继承ViewGroup
/**
* Created by cxj on 2016/3/26.
* 显示任何View的九宫格控件
* 这个控件将如何测量和排列孩子的逻辑给抽取了出来,针对有些时候需要使用九宫格形式来展示的效果
* 特别说明:此控件的包裹效果和填充父容器的效果是一样的,因为在本测量方法中并没有处理包裹的形式,也不能处理
* 针对在listview的条目item中的时候,传入的高度的测量模式为:{@link MeasureSpec#UNSPECIFIED},此时高度就就根本孩子的个数来决定了
* 因为不同的孩子格式,孩子的排列方式不一样
*/
public class CommonNineView extends ViewGroup {
public CommonNineView(Context context) {
this(context, null);
}
public CommonNineView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CommonNineView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
}
这是最开始你应该做的,选择继承的View对象,上面是我写的一些注释,有兴趣也可以读读,增加对此控件的理解
重写测量的方法onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取推荐的宽高和计算模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//获取孩子的个数
int childCount = getChildCount();
if (heightMode == MeasureSpec.UNSPECIFIED) { //出现在listView的item中
if (childCount == 1 || childCount == 3 || childCount == 4 || childCount > 6) {
heightSize = widthSize;
}
if (childCount == 2) {
heightSize = widthSize / 2;
}
if (childCount == 5 || childCount == 6) {
heightSize = widthSize * 2 / 3;
}
}
//保存自身的大小
setMeasuredDimension(widthSize, heightSize);
if (childCount == 1) { //一个孩子的时候
getChildAt(0).measure(MeasureSpec.makeMeasureSpec(widthSize - 2 * intervalDistance, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize - 2 * intervalDistance, MeasureSpec.EXACTLY));
}
if (childCount == 2) { //两个孩子的时候
getChildAt(0).measure(MeasureSpec.makeMeasureSpec((widthSize - 3 * intervalDistance) / 2, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize - 2 * intervalDistance, MeasureSpec.EXACTLY));
getChildAt(1).measure(MeasureSpec.makeMeasureSpec((widthSize - 3 * intervalDistance) / 2, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize - 2 * intervalDistance, MeasureSpec.EXACTLY));
}
if (childCount == 3 || childCount == 4) { //三个四个孩子的时候
for (int i = 0; i < childCount; i++) {
getChildAt(i).measure(MeasureSpec.makeMeasureSpec((widthSize - 3 * intervalDistance) / 2, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec((heightSize - 3 * intervalDistance) / 2, MeasureSpec.EXACTLY));
}
}
if (childCount == 5 || childCount == 6) { //五个六个孩子的时候
for (int i = 0; i < childCount; i++) {
getChildAt(i).measure(MeasureSpec.makeMeasureSpec((widthSize - 4 * intervalDistance) / 3, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec((heightSize - 3 * intervalDistance) / 2, MeasureSpec.EXACTLY));
}
}
if (childCount == 7 || childCount == 8 || childCount == 9) { //七个八个九个孩子的时候
for (int i = 0; i < childCount; i++) {
getChildAt(i).measure(MeasureSpec.makeMeasureSpec((widthSize - 4 * intervalDistance) / 3, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec((heightSize - 4 * intervalDistance) / 3, MeasureSpec.EXACTLY));
}
}
if (childCount > 9) {
throw new RuntimeException("the chlid count can not > 9");
}
}
咋一看测量的代码这么长呢,这是因为这里面很多代码都是针对不同孩子个数的时候,推荐给孩子的宽高也是不一样的
比如说一个孩子的时候,那就是和父容器一样大小啦,两个孩子的时候,每个孩子的宽度就是父容器的一半,这个不难理解
但是这里有一个判断是判断控件的高度计算模式为MeasureSpec.UNSPECIFIED的时候,这种模式一般出现在ListView中,因为ListView没有较好的高度值可以推荐给你.
那么在这种情况下,我们高度就根据孩子的个数和宽度进行一个关联就可以了
比如一个孩子的时候,高度和宽度一样
两个孩子的时候是控件的高度是控件的宽度的一半,这样子就能保证每个孩子的宽高都是一致的
ps:不理解的童鞋看这里
两张图分别是一个孩子和两个孩子的时候,可以看到,如果我们要求图片的宽高一致,那么控件的宽度和高度在孩子个数为两个的时候就是1:2的比例,当孩子个数是其他数字的时候类推
setMeasuredDimension(widthSize, heightSize);是保存控件的宽高的方法,不能省略,省略掉控件不显示,也就是宽高都为0
重写onLayout()方法安排孩子的位置
/**
* 安排孩子的位置
*
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
computeViewsLocation();
// 循环集合中的各个菜单的位置信息,并让孩子到这个位置上
for (int i = 0; i < getChildCount(); i++) {
// 循环中的位置
RectEntity e = rectEntityList.get(i);
// 循环中的孩子
View v = getChildAt(i);
// 让孩子到指定的位置
v.layout(e.leftX, e.leftY, e.rightX, e.rightY);
}
}
我们看到代码很少,这里博主对代码进行了一定的分离,由于onLayout是对孩子的位置进行排列,那么肯定事先知道孩子的坐标信息
所以这里的computeViewsLocation();正是计算所有孩子的位置信息的,每个位置信息用一个对象RectEntity表示
/**
* 一个实体类,描述一个矩形的左上角的点坐标和右下角的点的坐标
*
* @author cxj QQ:347837667
* @date 2015年12月22日
*
*/
public class RectEntity {
// 左上角横坐标
public int leftX;
// 左上角纵坐标
public int leftY;
// 右下角横坐标
public int rightX;
// 右下角纵坐标
public int rightY;
}
其实就是左上角的坐标和右下角的坐标,由于孩子都可以看成是水平放置的一个矩形,所以这两点足够决定一个矩形了
计算的代码如下:
/**
* 用于计算孩子们的位置信息
*/
private void computeViewsLocation() {
int childCount = getChildCount();
if (childCount == 0) {
return;
}
if (childCount == rectEntityList.size()) {
return;
}
rectEntityList.clear();
//获取到宽度和高度
mWidth = getWidth();
mHeight = getHeight();
switch (childCount) {
case 1:
oneView();
break;
case 2:
twoView();
break;
case 3:
threeView();
break;
case 4:
fourView();
break;
default:
other();
break;
}
}
根据孩子的个数不同,调用不同的计算过程,这里博主为了代码更清晰,没有采用复杂的算法,代码量虽然上去了,但是对于博主来说代码更清晰了
这里放出当孩子个数是一个和两个的时候的计算代码
/**
* 用于计算一个孩子的时候
*/
private void oneView() {
RectEntity r = new RectEntity();
r.leftX = intervalDistance;
r.leftY = intervalDistance;
r.rightX = r.leftX + getChildAt(0).getMeasuredWidth();
r.rightY = r.leftY + getChildAt(0).getMeasuredHeight();
rectEntityList.add(r);
}
/**
* 两个孩子的时候
*/
private void twoView() {
RectEntity one = new RectEntity();
one.leftX = intervalDistance;
one.leftY = intervalDistance;
one.rightX = one.leftX + getChildAt(0).getMeasuredWidth();
one.rightY = one.leftY + getChildAt(0).getMeasuredHeight();
rectEntityList.add(one);
RectEntity two = new RectEntity();
two.leftX = intervalDistance + one.rightX;
two.leftY = intervalDistance;
two.rightX = two.leftX + getChildAt(1).getMeasuredWidth();
two.rightY = two.leftY + getChildAt(1).getMeasuredHeight();
rectEntityList.add(two);
}
intervalDistance是孩子之间的间距,代码不难,就是比较复杂,大家写的时候注意一点,别弄错了,其他孩子个数的代码就不贴出来了,都是雷同的
小总结
这样子一个自定义的九宫格的控件就写好了,如果认真看的人应该明白,这个控件不仅仅针对于显示图片,上述的代码适用于任何一个想要九宫格形式的地方,这里针对的是View,并不是ImageView,所以你可以是ImageView,也可以是Facebook的fresco,都是可以的,另外孩子的如果排列,你可以自己自行更改的,不一定按照我写的这样子的排列
源码和demo下载
https://github.com/xiaojinzi123/android-demos/tree/master/NineViewDemo