Android 九宫格控件的制作之旅

来源:互联网 发布:选修课 知乎 编辑:程序博客网 时间:2024/04/30 14:33

前言

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

4 0
原创粉丝点击