前言
在博主的一个小项目中,需要实现动态列表中的条目有显示多张图片的功能,目前在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); } }
@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
/** * 安排孩子的位置 * * @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,都是可以的,另外孩子的如果排列,你可以自己自行更改的,不一定按照我写的这样子的排列
https://github.com/xiaojinzi123/android-demos/tree/master/NineViewDemo