手把手带你画一个漂亮蜂窝view Android自定义view

上一篇做了一个水波纹view  不知道大家有没有动手试试呢点击打开链接

这个效果做起来好像没什么意义,如果不加监听回调 图片就能直接替代。写这篇博客的目的是锻炼一下思维能力,以更好的面多各种自定义view需求。

转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50554058

本文是和代码同步写的。也就是说在写文章的时候才敲的代码。这样会显得文章有些许混乱。但是我想这样记录下来,一个自定义view的真正的制作过程,是一点一点,一步步跟着思路的改变,完善的。不可能一下子就做出一个完整的view。。技术也是这样,不可能一步登天。都是一步一步的积累。

另外,每一篇博客都是建立在之前博客的基础知识上的,如果你刚接触自定义view。可以来说说自定义view简单学习的方式这里看我以前的文章。记录了我学习自定义view的过程,而且前几篇博客或多或少犯了一些错误。这里我并不想改正博文中的错误,因为些错误是大家经常会犯的,后来的博客都有指出这些错误,以及不再犯,这是一个学习的过程。所以我想把错误的经历记录下来。等成为高手
回头看看当年的自己是多么菜。。也会有成就感。。

老规矩效果图如下:

首先画一个六边形,画之前来计算一下六边形的相关知识:

假设一个正六边形的边长为a  ,因为每个角都是120°  所以可得高为根号三a  ,如图所示。

有了这些信息我们就可以绘制一个六边形出来,如下:

 float height = (float) (Math.sqrt(3)*mLength);
        mPath.moveTo(mLength/2,0);
        mPath.lineTo(0,height/2);
        mPath.lineTo(mLength/2,height);
        mPath.lineTo((float) (mLength*1.5),height);
        mPath.lineTo(2*mLength,height/2);
        mPath.lineTo((float) (mLength*1.5),0);
        mPath.lineTo(mLength/2,0);
        mPath.close();

绘制效果:

然后将其根据一个偏移量进行平移,就可以用循环绘制出多个六边形

这里offset是偏移量,紧挨着的话应该是偏移一个六边形的宽,宽由上图可知为 a/2+a+a/2 即 2a;

 for(int i = 0 ; i < 3;i++) {
            int offset = mLength * 2 * i;
            mPath.moveTo(mLength / 2 + offset, 0);
            mPath.lineTo(0 + offset, height / 2);
            mPath.lineTo(mLength / 2 + offset, height);
            mPath.lineTo((float) (mLength * 1.5) + offset, height);
            mPath.lineTo(2 * mLength + offset, height / 2);
            mPath.lineTo((float) (mLength * 1.5)+offset, 0);
            mPath.lineTo(mLength / 2+offset, 0);
            mPath.close();
        }

发现效果如下

这不对啊,很奇怪啊。。 底下空出来的一个三角形放不下我们的第二行啊。。

那么怎么办呢。。 加大offset!  加大多少呢。。 应该多增加一个边长。。这样就正好留空了。 来试试

现在来准备画第二行....

发现我们之前path的坐标都是相对写死的。。 所以要回过头改一下,改成给定一个起点,就可以绘制出一个六边形,经过计算,得出下图

这里a代表边长。

改完之后的代码是:

 float height = (float) (Math.sqrt(3)*mLength);
        for(int i = 0 ; i < 3;i++) {

            //横坐标偏移量
            int offset = mLength * 3 * i ;
            //左上角的x
            int x = mLength/2 + offset;
            int y = 0;

            //根据左上角一点 绘制整个正六边形
            mPath.moveTo(x, y);
            mPath.lineTo(x -mLength/2, height / 2 + y);
            mPath.lineTo(x, height+y);
            mPath.lineTo(x + mLength, height +y);
            mPath.lineTo((float) (x + 1.5*mLength), height / 2+y);
            mPath.lineTo(x + mLength, y);
            mPath.lineTo(x, y);
            mPath.close();
        }

绘制出来的效果是一样的。但是方法以及变了。

然后来画第二行,第二行起点的path应该在这里

坐标是: 2a , height/2  这里的偏移量不变。

首先将画path的方法提取出来(as快捷键ctrl + alt + m)

 //根据左上角一点 绘制整个正六边形
    private void getPath(float height, float x, float y) {
        mPath.moveTo(x, y);
        mPath.lineTo(x -mLength/2, height / 2 + y);
        mPath.lineTo(x, height+y);
        mPath.lineTo(x + mLength, height +y);
        mPath.lineTo((float) (x + 1.5*mLength), height / 2+y);
        mPath.lineTo(x + mLength, y);
        mPath.lineTo(x, y);
        mPath.close();
    }

然后再给个循环,来绘制第二行的六边形

        for(int i = 0;i<2;i++){

            float offset = mLength * 3 * i ;
            float x = mLength*2 + offset;
            float y = height/2;

            getPath(height,x,y);

        }

        canvas.drawPath(mPath,mPaint);

得到如下的效果。

现在ondraw的全部代码如下:

  @Override
    protected void onDraw(Canvas canvas) {
        mPaint.setColor(Color.parseColor("#FFBB33"));
        //正六边形的高
        float height = (float) (Math.sqrt(3)*mLength);
        for(int i = 0 ; i < 3;i++) {

            //横坐标偏移量
            float offset = mLength * 3 * i ;
            //左上角的x
            float x = mLength/2 + offset;
            float y = 0;

            getPath(height, x, y);
        }

        canvas.drawPath(mPath,mPaint);
        mPath.reset();

        mPaint.setColor(Color.parseColor("#AA66CC"));

        for(int i = 0;i<2;i++){

            float offset = mLength * 3 * i ;
            float x = mLength*2 + offset;
            float y = height/2;

            getPath(height,x,y);

        }

        canvas.drawPath(mPath,mPaint);
    }

接下来对每行的个数进行一下控制。

  //每行的个数
    private int mColumnsCount = 3;

    //行数
    private int mLineCount = 3;

对应的循环也改变,最外面套一个大循环,来控制多行绘制

 for (int j = 0; j < mLineCount; j++) {
         if(j%2 == 0)  绘制奇数行 else 绘制偶数行

}

现在整个ondraw如下。

@Override
    protected void onDraw(Canvas canvas) {
        //正六边形的高
        float height = (float) (Math.sqrt(3) * mLength);

        for (int j = 0; j < mLineCount; j++) {
            if (j % 2 == 0) {
                mPaint.setColor(Color.parseColor("#FFBB33"));

                for (int i = 0; i < mColumnsCount; i++) {

                    //横坐标偏移量
                    float offset = mLength * 3 * i;
                    //左上角的x
                    float x = mLength / 2 + offset;
                    float y = j * height / 2;

                    getPath(height, x, y);

                }
                canvas.drawPath(mPath, mPaint);
                mPath.reset();
            } else {

                mPaint.setColor(Color.parseColor("#AA66CC"));

                for (int i = 0; i < mColumnsCount; i++) {

                    float offset = mLength * 3 * i;
                    float x = mLength * 2 + offset;
                    float y = (height / 2) * j;

                    getPath(height, x, y);

                }

                canvas.drawPath(mPath, mPaint);
                mPath.reset();
            }

        }

    }

好像颜色一样就不好看了。。那我们来动态改变一下颜色..

添加一个属性list来存放color

    private ArrayList<Integer> mColorList;
 mColorList = new ArrayList<>();

        mColorList.add(Color.parseColor("#33B5E5"));

        mColorList.add(Color.parseColor("#AA66CC"));

        mColorList.add(Color.parseColor("#99CC00"));

        mColorList.add(Color.parseColor("#FFBB33"));

        mColorList.add(Color.parseColor("#FF4444"));

在循环中,取出颜色值

 for (int j = 0; j < mLineCount; j++) {

            mPaint.setColor(mColorList.get(j));

效果如下:

嗯。。看起来像一点样子了。。。 给中间加点文字吧。。

先给每个蜂窝编号

按上面的循环   j为行数  i为列数

研究规律发现   编号等于 j*3 + i 

我们有六边形左上角的坐标xy 可以轻易的计算出中心坐标

这些都有了。开一个list存放中间的文字:

    //存放文字的list
    private ArrayList<String> mTextList ;

在初始化的时候给添加点数据

  mTextList = new ArrayList<>();
        for(int i =0;i<mLineCount*mColumnsCount;i++){
            mTextList.add("wing "+i);
        }

        mTextPaint = new Paint();
        mTextPaint.setTextSize(20);

绘制文字: 这里要注意他和path的绘制顺序,如果path后绘制则会覆盖掉文字

 float txtLength = mTextPaint.measureText(mTextList.get(txtId));
                    canvas.drawText(mTextList.get(txtId),x+mLength/2-txtLength/2,y+height/2+5, mTextPaint);

下面是全部的ondraw

  @Override
    protected void onDraw(Canvas canvas) {
        //正六边形的高
        float height = (float) (Math.sqrt(3) * mLength);
        for (int j = 0; j < mLineCount; j++) {

            mPaint.setColor(mColorList.get(j));

            if (j % 2 == 0) {

//                mPaint.setColor(Color.parseColor("#FFBB33"));

                for (int i = 0; i < mColumnsCount; i++) {
                    int txtId = j*3 +i;
                    //横坐标偏移量
                    float offset = mLength * 3 * i;
                    //左上角的x
                    float x = mLength / 2 + offset;
                    float y = j * height / 2;

                    mPath.reset();
                    getPath(height, x, y);

                    canvas.drawPath(mPath, mPaint);
                    float txtLength = mTextPaint.measureText(mTextList.get(txtId));
                    canvas.drawText(mTextList.get(txtId),x+mLength/2-txtLength/2,y+height/2+5, mTextPaint);

                }
            } else {

//                mPaint.setColor(Color.parseColor("#AA66CC"));

                for (int i = 0; i < mColumnsCount; i++) {

                    int txtId = j*3 +i;
                    float offset = mLength * 3 * i;
                    float x = mLength * 2 + offset;
                    float y = (height / 2) * j;
                    mPath.reset();
                    getPath(height, x, y);
                    canvas.drawPath(mPath, mPaint);
                    float txtLength = mTextPaint.measureText(mTextList.get(txtId));
                    canvas.drawText(mTextList.get(txtId),x+mLength/2-txtLength/2,y+height/2+5, mTextPaint);

                }

            }

        }

    }

现在的效果图如下:

好,那现在让他灵活一点。添加各种set方法,比如行数啊 列数啊  边长啊 文字内容啊 颜色啊之类的。

  /**
     * 设置列数
     * @param mColumnsCount
     */
    public void setColumnsCount(int mColumnsCount) {
        this.mColumnsCount = mColumnsCount;
        invalidate();
    }

    /**
     * 设置行数
     * @param mLineCount
     */
    public void setLineCount(int mLineCount) {
        this.mLineCount = mLineCount;

        invalidate();
    }

    /**
     * 设置文本数据
     */
    public void setTextList(ArrayList<String> textList) {
        mTextList.clear();
        mTextList.addAll(textList);

        invalidate();
    }

    /**
     * 设置颜色数据
     * @param colorList
     */
    public void setColorList(ArrayList<Integer> colorList) {
        mColorList.clear();
        mColorList.addAll(colorList);

        invalidate();
    }

然后 你有没有忘记测量呢? 只要把最外面的矩形大小给他就行

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if(widthMode == MeasureSpec.AT_MOST){
            widthSize = (int) ((3f*mColumnsCount+0.5f) *mLength);
        }else{
//            throw new IllegalStateException("only support wrap_content");
        }

        if(heightMode == MeasureSpec.AT_MOST){
            heightSize = (int) ((mLineCount/2f +0.5f) * (Math.sqrt(3) * mLength));
        }else{

//            throw new IllegalStateException("only support wrap_content");
        }

        setMeasuredDimension(widthSize,heightSize);

    }

这下使用wrap_content 来看看view的大小:

嗯。。测量也对着。。。   这里我只实现了wrap_content  大家可以以及扩展 让他支持EXACTLY

这样 一个蜂窝煤的view 就完成了。。。但是好像没鸟用的样子。。因为没有交互的话。。图片完全可以代替。所以这次就先遗留一个问题,事件的处理。其实逻辑也不是很复杂,就是判断触摸点 是否在Path内,如果action_up的时候在,分开编号,按照编号进行回调即可,这个问题,准备下篇博客解决,请大家继续关注我的博客 蟹蟹!。

本项目地址:点击打开链接

如果你觉得我写的还不错,请点击一下顶,继续关注我。谢谢!!

时间: 2024-10-12 20:06:28

手把手带你画一个漂亮蜂窝view Android自定义view的相关文章

手把手带你画一个动态错误提示 Android自定义view

嗯..再差1篇就可以获得持之以恒徽章了,今天带大家画一个比较简单的view. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50477108 废话不多说,看效果图: 首先 构造函数 测量... 这里就一笔带过了. public ErrorView(Context context) { this(context, null); } public ErrorView(Context context, AttributeSet attrs

手把手带你做一个超炫酷loading成功动画view Android自定义view

写在前面: 本篇可能是手把手自定义view系列最后一篇了,实际上我也是一周前才开始真正接触自定义view,通过这一周的练习,基本上已经熟练自定义view,能够应对一般的view需要,那么就以本篇来结尾告一段落,搞完毕设的开题报告后去学习新的内容. 有人对我说类似的效果网上已经有了呀,直接拿来就可以用,为什么还要写.我个人的观点是:第三方控件多数不能完全满足UI的要求,如果需要修改,那么必须理解他的实现,所以很有必要自己去写一款出来,成为程序的创造者,而不单单是使用者.所以,写一写已经实现的效果,

手把手带你画一个 时尚仪表盘 Android 自己定义View

拿到美工效果图.咱们程序猿就得画得一模一样. 为了不被老板喷,仅仅能多练啊. 听说你认为前面几篇都so easy,那今天就带你做个相对照较复杂的. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50468674 注意:每一篇博客都是建立在之前博客的基础知识上的,假设你刚接触自己定义view.能够来说说自己定义view简单学习的方式这里看我曾经的文章.记录了我学习自己定义view的过程,并且前几篇博客或多或少犯了一些错误(反复绘制,o

手把手教你打造一个心电图效果View Android自定义View

大家好,看我像不像蘑菇-因为我在学校呆的发霉了. 思而不学则殆 丽丽说得对,我有奇怪的疑问,大都是思而不学造成的,在我书读不够的情况下想太多,大多等于白想,所以革命没成功,同志仍需努力. 好了废话不说了,由于布总要做一个心电图的玩意,所以做来练练手,总之拿到的UI图如下: 做好的效果如下: 拿到图,先做一些简单的分析.呃.. 背景表格的绘制 心电图的绘制 背景表格的绘制: 首先drawColor黑色,然后用循环来画线. 心电图的绘制: 看样子是path,应该没问题. 于是就大干一番,按照这俩步骤

手把手教你画一个 逼格满满圆形水波纹loadingview Android

才没有完结呢o( ̄︶ ̄)n .大家好,这里是番外篇. 拜读了爱哥的博客,又学到不少东西.爱哥曾经说过: 要站在巨人的丁丁上. 那么今天,我们就站在爱哥的丁丁上来学习制作一款自定义view(开个玩笑,爱哥看到别打我). 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50523713 上一篇 带领大家做了一款炫酷的loading动画view 手把手带你做一个超炫酷loading成功动画view  不知道大家跟着做了一遍没有呢? 在开始之

我的Android进阶之旅------&gt;Android自定义View实现带数字的进度条(NumberProgressBar)

今天在Github上面看到一个来自于 daimajia所写的关于Android自定义View实现带数字的进度条(NumberProgressBar)的精彩案例,在这里分享给大家一起来学习学习!同时感谢daimajia的开源奉献! 第一步.效果展示 图1.蓝色的进度条 图2.红色的进度条 图3.多条颜色不同的进度条 图4.多条颜色不同的进度条 版权声明:本文为[欧阳鹏]原创文章,欢迎转载,转载请注明出处! [http://blog.csdn.net/ouyang_peng/article/deta

[原] Android 自定义View步骤

例子如下:Android 自定义View 密码框 例子 1 良好的自定义View 易用,标准,开放. 一个设计良好的自定义view和其他设计良好的类很像.封装了某个具有易用性接口的功能组合,这些功能能够有效地使用CPU和内存,并且十分开放的.但是,除了开始一个设计良好的类之外,一个自定义view应该: l 符合安卓标准 l 提供能够在Android XML布局中工作的自定义样式属性 l 发送可访问的事件 l 与多个Android平台兼容. Android框架提供了一套基本的类和XML标签来帮您创

Android自定义View(CustomCalendar-定制日历控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeasure 4onDraw 绘制月份 绘制星期 绘制日期及任务 5事件处理 源码下载 ??应项目需求,需要做一个日历控件,效果图如下: ???? ??接到需求后,没有立即查找是否有相关开源日历控件可用.系统日历控件是否能满足 ,第一反应就是这个控件该怎么画?谁叫咱自定义控件技术牛逼呢O(∩_∩)O哈哈~

android自定义View之钟表诞生记

很多筒子觉得自定义View是高手的象征,其实不然.大家觉得自定义View难很多情况下可能是因为自定义View涉及到了太多的类和API,把人搞得晕乎乎的,那么今天我们就从最简单的绘图API开始,带大家来一步一步深入自定义View的世界. 先来看看我们今天要实现的一个效果图: 整个效果很简单,就是在屏幕上显示一个钟表,该钟表可以自动走动. OK,那就开始动工吧. 1.准备工作 首先,要实现这个时钟,我得继承自View来自己绘制时钟,因为这种效果没有办法继承已有控件去完善功能.然后我们来看看我们这里需