Android自定义View——一个可定制的六边形阵列

关于六边形的自定义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的自适应效果


Android自定义View——一个可定制的六边形阵列_第1张图片
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

你可能感兴趣的:(Android自定义View——一个可定制的六边形阵列)