关于六边形的自定义View网上已经有很多了,但目前来看都是固化的UI,可定制性不高,所以我这里将六边形与坐标绑定,这样的话我们就可以随意组合六边形形成我们需要的一个图案。

基本思路也很简单,一句话——明确行的标准。因为六边形组合起来的行是不规整的,它的列是规整的。那坐标由谁来控制呢?这里我选择父级容器,也就是说我们为这个自定义的View再自定义一个ViewGroup。X,Y坐标是view的自定义属性,viewgroup读出坐标数据进行测量与布局。

六边形的话很好画,简单的正切关系,然后利用path做路径。

float cx=getWidth()/2;

float cy=getHeight()/2;

float length=cx-PADDING;

float a=(float) Math.sqrt(3)*length/2; //邻边长度

mPaint.setColor(mColor);

mPaint.setAlpha(mAlpha);

mPath.moveTo(PADDING,cy);

//画一个正六边形

mPath.lineTo(PADDING+length/2f,cy-a);

mPath.lineTo(3/2f*length+PADDING,cy-a);

mPath.lineTo(cx+length,cy);

mPath.lineTo(3/2f*length+PADDING,cy+a);

mPath.lineTo(PADDING+length/2f,cy+a);

mPath.lineTo(PADDING,cy);

canvas.drawPath(mPath,mPaint);

if(mText!=null){

mPaint.setTextAlign(Paint.Align.CENTER);

mPaint.setTextSize(DisplayUtil.sp2px(mContext,mTextSize));

mPaint.setColor(mTextColor);

//文字居中显示

Paint.FontMetricsInt fontMetricsInt=mPaint.getFontMetricsInt();

float baseline=cy+(fontMetricsInt.descent-fontMetricsInt.ascent)/2-fontMetricsInt.descent;

canvas.drawText(mText,cx,baseline,mPaint);

}

然后我们再做一个简单的点击效果,我这里选择将透明度减半,效果还是挺明显的

@Override

public boolean onTouchEvent(MotionEvent event) {

int action=event.getAction();

Point point=new Point((int)event.getX(),(int)event.getY());

switch (action){

case MotionEvent.ACTION_DOWN:{

if(isInHexagon(mPath,point)){

mAlpha=150;

listener.onClick(getMX(),getMY());

invalidate();

}

break;

}

case MotionEvent.ACTION_UP:{

mAlpha=255;

invalidate();

break;

}

default:

break;

}

return true;

}

这里还涉及到一个不规则(也可以说是非矩形)图形的边界判定问题,之前我也不太清楚,这里我们应该使用Region的setPath与contains这两个API

/**

* 判断点是否在边界内

* @param path

* @param point

* @return

*/

private boolean isInHexagon(Path path,Point point){

RectF bounds=new RectF();

path.computeBounds(bounds,true);

Region region=new Region();

region.setPath(path,new Region((int)bounds.left,(int)bounds.top,

(int)bounds.right,(int)bounds.bottom));

return region.contains(point.x,point.y);

}

最后给外部提供一个接口

public void setOnClickHVListener(onClickHVListener listener){

this.listener=listener;

}

public interface onClickHVListener{

void onClick(int x,int y);

}

到这里我们的自定义View就完成了,然后就是自定义的一个Viewgroup。与自定义View一样需要考虑测量时的自适应问题。对于我们这个六边形阵列来说,我们很清楚,最大的X坐标将控制容器的宽度,最大的Y坐标将控制容器的高度(其实在高度的问题上还有一种特殊情况是因为行的不规整造成的,也就是说最大Y坐标可能有两个高度,我们只能选最高的那个)

private int measureSize(int measureSpec,boolean isWidth){

int measureSize;

int mode= View.MeasureSpec.getMode(measureSpec);

int size=View.MeasureSpec.getSize(measureSpec);

if(mode==MeasureSpec.EXACTLY)

measureSize=size;

else {

measureSize = 0;

int count=getChildCount();

HexagonView view;

view=(HexagonView)getChildAt(0);

int maxX=view.getMX(),maxY=view.getMY();

if(count==1){

//当只有一个子View的时候强制设定为子View的大小

measureSize=(isWidth?mChildWidth:mChildHeight);

}else if(count>0) {

//遍历获得最大X、Y坐标

for (int i = 1; i < count; i++) {

view=(HexagonView)getChildAt(i);

int mX=view.getMX(),mY=view.getMY();

if(mX>maxX)

maxX=mX;

if(mY>=maxY) {

maxY = mY;

}

}

boolean temp=false; //获取最大Y坐标并不能表示高度为Y个子View

//判断最大Y坐标的一行是否有奇数的X坐标(突出一半的六边形)

for (int i = 1; i < count; i++) {

view = (HexagonView) getChildAt(i);

int mX = view.getMX(), mY = view.getMY();

if(mY==maxY&&mX%2==1)

temp=true;

}

int line = mChildWidth / 2;

//宽度的计算分X坐标为奇数和偶数两种情况

if(isWidth){

if(maxX%2==0)

measureSize = (maxX / 2 + 1) * mChildWidth + line * maxX / 2;

else

measureSize=(maxX+1)/2*mChildWidth+(maxX-1)/2*line+(mChildWidth-line/2);

}else{//高度的计算同样分两种情况(是否突出一半的六边形)

if(temp)

measureSize=mChildHeight*(maxY+1)-maxY*2*(mChildHeight/2-(int)(line/2*Math.sqrt(3)))

+(int)(line/2*Math.sqrt(3));

else

measureSize=mChildHeight*(maxY+1)-maxY*2*(mChildHeight/2-(int)(line/2*Math.sqrt(3)));

}

}

}

return measureSize;

}

然后是layout方法,因为这个阵列的行不规则,列是规则的,所以我们只要搞定了行,列就好说了(垂直平移嘛),所以我们选择从每一行入手。

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

int count=getChildCount();

HexagonView view;

for(int i=0;i

然后我们将之前view的接口在viewgroup里面实现,目的是让Activity通过viewgroup可以操作所有的view

HexagonsLayout extends ViewGroup implements HexagonView.onClickHVListener

···

···

private onClickItemListener listener;

public void setOnClickItemListener(onClickItemListener listener){

this.listener=listener;

}

public interface onClickItemListener{

void onClickItem(int x,int y);

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

int count=getChildCount();

HexagonView hexagonView;

for(int i=0;i

最后我们在AS里看下viewgroup的自适应效果

b20457ad1bbee41a2c02bae69b500b42.png

image.png

还有点击事件的效果

未命名.gif

源码地址

https://github.com/Geek-L/AndroidProject/blob/master/CustomView/app/src/main/java/com/zyl/customview/view/HexagonView.java

https://github.com/Geek-L/AndroidProject/blob/master/CustomView/app/src/main/java/com/zyl/customview/viewgroup/HexagonsLayout.java

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐