Android 九宫格控件的制作之旅

前言

在博主的一个小项目中,需要实现动态列表中的条目有显示多张图片的功能,目前在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

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值