如下图:

Android 六边形ImageView 六边形面板生成_正余弦函数应用

其中的小六边形可以用一张图片替换,也可以用代码直接绘制,这里就不说具体绘制小六边形了,上面的数字是调试用的,可以忽略。 

要产生一个这样的图形,有三种方式。

1、按列遍历生成,分左边,右边两部分,每部分按照对应的列产生对应的六边形,再分析每列小六边形个数规律,每列小六边形个数一次递增1或者一次递减1,这样的递减导致小六边形的位置变化,不能简单地按照行列索引去设置位置,对每列来说,水平方向的位置都是不变的,唯一需要调整的是竖直方向的位置,这看起来比较麻烦,但是也有规律的,仔细找能找到,这里不用这种方式

Android 六边形ImageView 六边形面板生成_六边形面盘_02

2、如上图,可以先生成三条蓝色线上的小六边形,因为三条蓝色线的是小六边形都与最中心的小六边形相连接,小六边形的坐标位置算起来很方便,直接通过遍历索引乘以对应的小六边形宽高,当然这里的宽不是真正的宽,因为斜线上的小六边形是斜着的,相对坐标轴来说两个小六边形的距离就不是它们的宽了,但是可以在编辑器上计算出来,这个值是固定的。三条蓝色线上的小六边形生成好后,就可以按区域(上图中①②③④⑤⑥)生成各自区域的小六边形了,这里又回到1了,分析每个区域的列数,按照列数产生各列的小六边形,这里也不用这种方式,这里要用的是下面的方式。 

3、按环生成,先生成一环,然后多次生成,就生成多个环了,拼起来就是一个盘面,如下图。

Android 六边形ImageView 六边形面板生成_六边形环_03

上图共7环,实际上是8环,中间还有一个,也算一环。

用正余弦来确定各区域小六边形的位置坐标,如图:

Android 六边形ImageView 六边形面板生成_六边形环_04

 对每一环来说,环上的小六边形的个数是确定的,每增加一环,小六边形的个数增加6个,每个小六边形相对于坐标原点的角度也是确定的(360/小六边形的个数),小六边形的位置则为(x=R * cos(a),y=R * sin(a));但是R的值是变化的,因为环并不是一个标准的圆,关键在于求出R的值,为了方便计算,将坐标系逆时针旋转30度,以上图中最外环为例,最外环的六边形42的角度为0(360),顺时针旋转,则六边形7的角度则为60,六边形14的角度为120,六边形21的角度为180,六边形28的角度为240,六边形35的角度为300。

还是以最外环为例,定义两个小六边形的高差为h,宽差为w,最外环有42个六边形,每相邻两个六边形之间的角度差为8.571428571428571(360/42),保留两位小数取8.57;

从起始点六边形42开始,分析各个小六边形的位置:

六边形42的位置为(R * cos(0),R * sin(0));

则六边形1-6的位置为(R * cos(0),R * sin(0)+i * h),i为六边形1-六边形6的索引(1,2,3,4,5,6);

六边形7的位置为(R * cos(60),R * sin(60));

六边形8-13的位置为(R * cos(60)-i*w,R * sin(60)+i * h),i为六边形8-六边形13的索引(1,2,3,4,5,6);

六边形14的位置为(R * cos(120),R * sin(120));

六边形15-20的位置为(R * cos(120)-i*w,R * sin(60)-i * h),i为六边形15-六边形20的索引(1,2,3,4,5,6);

六边形21的位置为(R * cos(180),R * sin(180));

六边形22-27的位置为(R * cos(180),R * sin(180)-i * h),i为六边形15-六边形20的索引(1,2,3,4,5,6);

六边形28的位置为(R * cos(240),R * sin(240));

六边形29-34的位置为(R * cos(240)+i*w,R * sin(240)-i * h),i为六边形29-六边形34的索引(1,2,3,4,5,6);

六边形35的位置为(R * cos(360),R * sin(360));

六边形36-41的位置为(R * cos(360)+i*w,R * sin(360)-i * h);

每个小六边形的位置都能方便的表示出来。这里为了分析清楚过程,写了很多步,但是代码里只需一个for循环。

上面的类似R * cos(a)+-i*w,R * sin(a)+-i * h,具体是加还是减跟坐标系的类型有关,要看坐标系的原点是在左下角还是左上角,上面是以laya的坐标系来的。

就分析到这里,下面是具体代码(TypeScript),小六边形用的一个预制体表示:

先定义一个常量类:

export default class Constants {
    public static readonly Row:number = 13;
    public static readonly Col:number = 9;
   //定义小六边形的宽度
    public static readonly HexItemWidth:number = 74;
    //定义小六边形的高度
    public static readonly HexItemHeight:number = 64;
    //定义小六边形的半宽度
    public static readonly HalfHexItemWidth:number = 37;
    //定义小六边形的高度
    public static readonly HalfHexItemHeight:number = 32;
    //定义相邻两小六边形的宽度间隔(就是上面分析说的宽度差)_
    public static readonly HexItemGap:number = 55;
    //定义边界角度
    public static readonly RightBottomAngle:number = 60;
    public static readonly BottomAngle:number = 120;
    public static readonly LeftBottomAngle:number = 180;
    public static readonly LeftAngle:number = 240;
    public static readonly LeftTopAngle:number = 300;
    public static readonly RightTopAngle:number = 360;
    public static readonly HexItemOffsetAngle:number = 30;
}

由于在进行sin,cos运算时,会产生一个小数,再乘以一个R后,会产生一个误差,表现是相邻两小六边形中间会出现一个小缝隙(一个环中会出现2-4个),主要是因为sin,cos那个小数产生的,所以保留两位小数予以解决,系统提供的接口保留两位小数的方法会进行四舍五入,所以定义一个数学类,主要用于保留两位小数,不进行四舍五入:

export default class MathUtils {
    /**
     * @param num 保留两位数,不进行四舍五人
     * @returns
     */
    public static to2Fixed(num:number):number{
        if(num >= 0) return Math.floor(num*100)/100;
        return Math.ceil(num*100)/100;
    }
    public static to2FixedOther(num:number):number{
       return Number(num.toFixed(3).slice(0, -1));
    }
}
六边形面盘,就是生成多个六边形环:
/**
     * 六边形面盘
     * @param round 该面盘由多少环组成
     */
    private createHexagonDish(round:number = 1):void{
        for(let i:number = 1;i<=round;i++){
            this.createHexagonCircle(i);
        }
    }
生成某一个环:
/**
     * 六边形环
     * @param roundIndex 环的索引 从1开始 1,2,3,4......;
     */
    private createHexagonCircle(roundIndex:number = 1){
        let centerX:number = Laya.stage.width/2;
        let centerY:number = Laya.stage.height/2;
        let r:number = (roundIndex - 1) * 64;
        let cos30:number = MathUtils.to2Fixed(Math.cos(Laya.Utils.toRadian(30)));
        let _rightBottomX:number = (r * cos30 > 0) ? Math.floor(r * cos30) : Math.ceil(r * cos30);
        let rigthBottomX:number = centerX + _rightBottomX;
        let rightBottomY:number = centerY + Math.round(r * Math.sin(Laya.Utils.toRadian(30)));
        let cos90:number = MathUtils.to2Fixed(Math.cos(Laya.Utils.toRadian(90)));
        let _bottomX:number = (r * cos90 > 0) ? Math.floor(r * cos90) : Math.ceil(r * cos90);
        let bottomX:number = centerX + _bottomX;
        let bottomY:number = centerY + Math.round(r * Math.sin(Laya.Utils.toRadian(90)));
        let cos150:number = MathUtils.to2Fixed(Math.cos(Laya.Utils.toRadian(150)));
        let _leftBottomX:number = (r * cos150 > 0) ? Math.floor(r * cos150) : Math.ceil(r * cos150);
        let leftBottomX:number = centerX + _leftBottomX;
        let leftBottomY:number = centerY + Math.round(r * Math.sin(Laya.Utils.toRadian(150)));
        let cos210:number = MathUtils.to2Fixed(Math.cos(Laya.Utils.toRadian(210)));
        let _leftTopX:number = (r * cos210 > 0) ? Math.floor(r * cos210) : Math.ceil(r * cos210);
        let leftTopX:number = centerX + _leftTopX;
        let leftTopY:number = centerY + Math.round(r * Math.sin(Laya.Utils.toRadian(210)));
        let cos270:number = MathUtils.to2Fixed(Math.cos(Laya.Utils.toRadian(270)));
        let _topX:number = (r * cos270 > 0) ? Math.floor(r * cos270) : Math.ceil(r * cos270);
        let topX:number = centerX + _topX;//Math.floor(r * Math.cos(Laya.Utils.toRadian(270)));
        let topY:number = centerY + Math.round(r * Math.sin(Laya.Utils.toRadian(270)));
        let cos330:number = MathUtils.to2Fixed(Math.cos(Laya.Utils.toRadian(330)));
        let _rightTopX:number = (r * cos330 > 0) ? Math.floor(r * cos330) : Math.ceil(r * cos330);
        let rightTopX:number = centerX + _rightTopX;
        let rightTopY:number = centerY + Math.round(r * Math.sin(Laya.Utils.toRadian(330)));
        //每增加一个环小六边形个数增加6
        let hexItemCount:number = (roundIndex == 1) ? 1 : (roundIndex - 1) * 6;
        //相邻两个小六边形的间隔角度(上面分析中的角度差)
        let hexItemAngle:number = 360/hexItemCount;
        let initAngle:number = (roundIndex%2 == 0) ? hexItemAngle : 0;
        let rCount:number = 0;
        let rbCount:number = 0;
        let lbCount:number = 0;
        let lCount:number = 0;
        let ltCount:number = 0;
        let rtCount:number = 0;
        for(let i:number = 0;i<hexItemCount;i++){
            let itemX:number = 0;
            let itemY:number = 0;
            let tempAngle:number = initAngle + hexItemAngle * i;
            //生成 右下角 下 左下角 左上角 上 右上角 节点
            if(tempAngle%60==0){
                r = (roundIndex - 1) * Constants.HexItemHeight;
                let radian:number = Laya.Utils.toRadian(tempAngle-Constants.HexItemOffsetAngle)//30   -Math.PI/6  每个节点偏移30度 具体可以根据需要调节;
                let fixed2Radian:number = MathUtils.to2Fixed(Math.cos(radian));
                let offsetX:number = r * fixed2Radian;
                let realOffsetX:number = (offsetX >= 0) ? Math.floor(offsetX) : Math.ceil(offsetX);
                itemX = centerX + realOffsetX;
                itemY = centerY + Math.round(r * Math.sin(radian));
            }else{
                //生成右边区域节点
                if(tempAngle < Constants.RightBottomAngle){
                    rCount++;
                    itemX = rightTopX;
                    itemY = rightTopY+rCount * Constants.HexItemHeight;
                }else if(tempAngle < Constants.BottomAngle){//生成右下角区域节点
                    rbCount++;
                    itemX = rigthBottomX-rbCount * Constants.HexItemGap;
                    itemY = rightBottomY+rbCount * Constants.HalfHexItemHeight;
                }else if(tempAngle < Constants.LeftBottomAngle){//生成左下角区域节点
                    lbCount++;
                    itemX = bottomX-lbCount * Constants.HexItemGap;
                    itemY = bottomY-lbCount * Constants.HalfHexItemHeight;
                }else if(tempAngle < Constants.LeftAngle){//生成左边区域节点
                    lCount++;
                    itemX = leftBottomX;
                    itemY = leftBottomY-lCount * Constants.HexItemHeight;
                }else if(tempAngle < Constants.LeftTopAngle){//生成左上角区域节点
                    ltCount++;
                    itemX = leftTopX+ltCount * Constants.HexItemGap;
                    itemY = leftTopY-ltCount * Constants.HalfHexItemHeight;
                }else if(tempAngle < Constants.RightTopAngle){//生成右上角区域节点
                    rtCount++;
                    itemX = topX+rtCount * Constants.HexItemGap;
                    itemY = topY+rtCount * Constants.HalfHexItemHeight;
                }
            }
                   this.createHexItem(itemX,itemY,i+1);
        }
    }
private createHexItem(posX:number,posY:number,index:number = 0):Laya.Image{
        //HexItem是一个预制体
        let hexItem:Laya.Image = this.HexItem.create();
        let aa:Laya.Text = hexItem.getChildByName("value") as Laya.Text;
        aa.text = index+"";
        this.owner.addChild(hexItem);
        hexItem.pos(posX,posY);
        return hexItem;
    }

测试一下:

创建一个环:this.createHexagonCircle(5),5是环的索引,从里向外为1,2,3............,如下图:

Android 六边形ImageView 六边形面板生成_正余弦函数应用_05

 创建一个环面盘:this.createHexagonDish(7),7为生成的环数如下图:

Android 六边形ImageView 六边形面板生成_正余弦函数应用_06