android 六边形蜂巢布局控件

来源:互联网 发布:中国基尼系数知乎 编辑:程序博客网 时间:2024/04/29 10:07

前言:最近新项目有个需求,实现蜂巢一样的布局界面。刚看到需求,心里卧槽了下,不过还挺好看的,于是思考了怎么实现。花了两三天时间,终于实现了跟我想要的差不多,封装成了比较容易拓展的使用方式。

需求效果如下:
1.蜂巢类型:

蜂巢布局

与正常图片切换效果:

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述


下面是实现思路:

怎么实现呢,认真观察,我们会发现其实整个视图单个item是按照这样的规律排布六边形挨着连接,:

这里写图片描述

所以我们的解决问题就转化为如何按照这样的规律排布。该界面复杂就复杂在一个一正六边形挨着连接,计算六边形的边以及点,显然会比较复杂,为了将问题简单化,我将正六边形转化为正方形,要知道,正六边形可以通过正方形的内切圆绘制出来:

这里写图片描述

所以这样就很简单了,上面的排布其实转化为正方形的排布了:

这里写图片描述

所以我们最终实现的思路是这样的: 绘制正方形区域并按照规律排布,在正方形区域绘制出正六边形。


接下来是实现过程

实现正方形按照指定规律排布,第一时间想到的是使用RecyclerView并重写RecyclerView.LayoutManager。关于RecyclerView不懂的请百度。

如何计算正方形排布的坐标,首先,我将一组视图分成了两小组:

这里写图片描述

为了拓展性,我们可以指定要显示的列数mColumnSize,这样我们就可以计算出第一小组最多显示个数以及第二小组最多显示个数:

 int rvwidth = getRecyclerViewWidth();//RecyclerView width        int itemWidth = rvwidth / mColumnSize;//正方形宽度        int itemHeight = itemWidth;        int firstgroupnum = mColumnSize / 2 ;//第一小组最多显示个数        int secondgroupnum = mColumnSize % 2 == 0 ? mColumnSize / 2 : mColumnSize / 2 + 1;//第二组最多显示个数

这样我们可以得到了内切圆半径R:

 float r = itemWidth / 2;//内切圆半径

整个组边的计算关系如下图:

这里写图片描述

我们很容易就得到以下关系:

w = r
fleft = (rvwidth - (itemWidth * firstgroupnum + r * (firstgroupnum - 1))) / 2;//第一组开始偏移量
sright = fleft-itemWidth*3/4;//第二组开始偏移量

通过上面的关系我们很容易就得到了这些正方形的分布坐标。那么开始撸代码了。

为了简单易于理解,封装一下每一组的数据,每一组的数据由两小组数据组成:

 private class GroupData{        int itemIndex = 0;        List<GroupItem> FirstGroup = new ArrayList<>();        List<GroupItem> SecondGroup = new ArrayList<>();    }    private class GroupItem{        Rect rect = null;        int itemindex = 0;    }

这样我们就可以根据第一小组显示的个数与第二小组显示的个数得到划分好的GroupData:

 /** 将数据转化为组数据     * @param firstgroupnum 第一小组最多显示个数     * @param secondgroupnum 第二小组最多显示个数     * @return     */    private List<GroupData> GetGroupData(int firstgroupnum, int secondgroupnum) {        int groupnums = getItemCount()/(firstgroupnum+secondgroupnum);        if(getItemCount()%(firstgroupnum+secondgroupnum)!=0){            groupnums++;        }        List<GroupData> groupdata = new ArrayList<>();        int ItemIndex = 0;        for(int index  = 0;index<groupnums;index++){            GroupData g = new GroupData();            for(int i=0;i<firstgroupnum;i++){                GroupItem item = new GroupItem();                item.itemindex = ItemIndex++;                item.rect = mItemFrames.get(item.itemindex);                g.FirstGroup.add(item);            }            for(int i=0;i<secondgroupnum;i++){                GroupItem item = new GroupItem();                item.itemindex = ItemIndex++;                item.rect = mItemFrames.get(item.itemindex);                g.SecondGroup.add(item);            }            groupdata.add(g);        }        return groupdata;    }

转化为组数据后,计算出上面的边关系:

 float fleft = (rvwidth - (itemWidth * firstgroupnum + r * (firstgroupnum - 1))) / 2;//第一组开始偏移量        float firstgroupitemleftposition = 0;//第一组item左边位置        float sright = fleft-itemWidth*3/4;//第二组开始偏移量        float d = (float) (itemHeight / 4 * (2 - Math.sqrt(3)));//六边形到边到内切圆的距离        float secondgroupmarginfirstgroup = (float) itemHeight/2 - d;        float topmargin = 50;        float toppositoion = 0;

然后遍历排布每组的正方形,每组的正方形有两小组,分别遍历排布:

 for(int index =0;index<groupDatas.size();index++){            toppositoion = index*itemHeight+topmargin+GROUP_PADDING*index;            toppositoion = toppositoion-d*2*index;            GroupData g = groupDatas.get(index);            for(int firstgroupindex =0;firstgroupindex<g.FirstGroup.size();firstgroupindex++){                int left = (int) (fleft+firstgroupindex*(itemWidth+r));                int top = (int) toppositoion;                int right = left+itemWidth;                int bottom = top+itemHeight;                Rect rect = g.FirstGroup.get(firstgroupindex).rect;                rect.set(left,top,right,bottom);            }            toppositoion = toppositoion+secondgroupmarginfirstgroup+FIRSTGROUP_MARGIN_SECONDGROUP;            for(int secondgroupindex =0;secondgroupindex<g.SecondGroup.size();secondgroupindex++){                int left = (int) (sright+secondgroupindex*(itemWidth+r));                int top = (int) toppositoion;                int right = left+itemWidth;                int bottom = top+itemHeight;                Rect rect = g.SecondGroup.get(secondgroupindex).rect;                rect.set(left,top,right,bottom);            }        }

注意的是,为了方便使用,我们对每组的视图增加了间距,每组的两个小组之间也增加了间距。

 public  int FIRSTGROUP_MARGIN_SECONDGROUP = 50;//第一小组与第二小组间距    public  int GROUP_PADDING = 120;//组距

分布好后,填充视图就是了:

private void fill(RecyclerView.Recycler recycler, RecyclerView.State state) {        if (getItemCount() <= 0 || state.isPreLayout()) {            return;        }        Rect displayRect = new Rect(mHorizontalOffset, mVerticalOffset,                getHorizontalSpace() + mHorizontalOffset,                getVerticalSpace() + mVerticalOffset);        for (int i = 0; i < getItemCount(); i++) {            Rect frame = mItemFrames.get(i);            if (Rect.intersects(displayRect, frame)) {                View scrap = recycler.getViewForPosition(i);                addView(scrap);                measureChildWithMargins(scrap, 0, 0);                layoutDecorated(scrap, frame.left - mHorizontalOffset, frame.top - mVerticalOffset,                        frame.right - mHorizontalOffset, frame.bottom - mVerticalOffset);            }        }    }

完成到这里后,我们就可以实现到这样的效果了:

间距为50显示5列的效果:
这里写图片描述

间距为50显示7列的效果:
这里写图片描述

剩下的最后一步,就是将正方形图片转化为正六边形而已,具体实现代码,参考我这篇文章:android六边形imageview

最终转化后得到效果如下:
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

转载注明:http://blog.csdn.net/u014614038/article/details/77497588

代码下载:github

原创粉丝点击