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

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

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

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

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

    老规矩效果图如下:

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

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

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

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

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

    1. float height = (float) (Math.sqrt(3)*mLength);
    2. mPath.moveTo(mLength/2,0);
    3. mPath.lineTo(0,height/2);
    4. mPath.lineTo(mLength/2,height);
    5. mPath.lineTo((float) (mLength*1.5),height);
    6. mPath.lineTo(2*mLength,height/2);
    7. mPath.lineTo((float) (mLength*1.5),0);
    8. mPath.lineTo(mLength/2,0);
    9. mPath.close();

    绘制效果:

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

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

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

    1. for(int i = 0 ; i < 3;i++) {
    2. int offset = mLength * 2 * i;
    3. mPath.moveTo(mLength / 2 + offset, 0);
    4. mPath.lineTo(0 + offset, height / 2);
    5. mPath.lineTo(mLength / 2 + offset, height);
    6. mPath.lineTo((float) (mLength * 1.5) + offset, height);
    7. mPath.lineTo(2 * mLength + offset, height / 2);
    8. mPath.lineTo((float) (mLength * 1.5)+offset, 0);
    9. mPath.lineTo(mLength / 2+offset, 0);
    10. mPath.close();
    11. }

    发现效果如下

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

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

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

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

    现在来准备画第二行….

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

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

    这里a代表边长。

    改完之后的代码是:

    1. float height = (float) (Math.sqrt(3)*mLength);
    2. for(int i = 0 ; i < 3;i++) {
    3. //横坐标偏移量
    4. int offset = mLength * 3 * i ;
    5. //左上角的x
    6. int x = mLength/2 + offset;
    7. int y = 0;
    8. //根据左上角一点 绘制整个正六边形
    9. mPath.moveTo(x, y);
    10. mPath.lineTo(x -mLength/2, height / 2 + y);
    11. mPath.lineTo(x, height+y);
    12. mPath.lineTo(x + mLength, height +y);
    13. mPath.lineTo((float) (x + 1.5*mLength), height / 2+y);
    14. mPath.lineTo(x + mLength, y);
    15. mPath.lineTo(x, y);
    16. mPath.close();
    17. }

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

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

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

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

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

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

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

    1. for(int i = 0;i<2;i++){
    2. float offset = mLength * 3 * i ;
    3. float x = mLength*2 + offset;
    4. float y = height/2;
    5. getPath(height,x,y);
    6. }
    7. canvas.drawPath(mPath,mPaint);

    得到如下的效果。

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

    现在ondraw的全部代码如下:

    1. @Override
    2. protected void onDraw(Canvas canvas) {
    3. mPaint.setColor(Color.parseColor("#FFBB33"));
    4. //正六边形的高
    5. float height = (float) (Math.sqrt(3)*mLength);
    6. for(int i = 0 ; i < 3;i++) {
    7. //横坐标偏移量
    8. float offset = mLength * 3 * i ;
    9. //左上角的x
    10. float x = mLength/2 + offset;
    11. float y = 0;
    12. getPath(height, x, y);
    13. }
    14. canvas.drawPath(mPath,mPaint);
    15. mPath.reset();
    16. mPaint.setColor(Color.parseColor("#AA66CC"));
    17. for(int i = 0;i<2;i++){
    18. float offset = mLength * 3 * i ;
    19. float x = mLength*2 + offset;
    20. float y = height/2;
    21. getPath(height,x,y);
    22. }
    23. canvas.drawPath(mPath,mPaint);
    24. }
    25.  

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

    1. //每行的个数
    2. private int mColumnsCount = 3;
    3. //行数
    4. private int mLineCount = 3;

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

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

    现在整个ondraw如下。

    1. @Override
    2. protected void onDraw(Canvas canvas) {
    3. //正六边形的高
    4. float height = (float) (Math.sqrt(3) * mLength);
    5. for (int j = 0; j < mLineCount; j++) {
    6. if (j % 2 == 0) {
    7. mPaint.setColor(Color.parseColor("#FFBB33"));
    8. for (int i = 0; i < mColumnsCount; i++) {
    9. //横坐标偏移量
    10. float offset = mLength * 3 * i;
    11. //左上角的x
    12. float x = mLength / 2 + offset;
    13. float y = j * height / 2;
    14. getPath(height, x, y);
    15. }
    16. canvas.drawPath(mPath, mPaint);
    17. mPath.reset();
    18. } else {
    19. mPaint.setColor(Color.parseColor("#AA66CC"));
    20. for (int i = 0; i < mColumnsCount; i++) {
    21. float offset = mLength * 3 * i;
    22. float x = mLength * 2 + offset;
    23. float y = (height / 2) * j;
    24. getPath(height, x, y);
    25. }
    26. canvas.drawPath(mPath, mPaint);
    27. mPath.reset();
    28. }
    29. }
    30. }

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

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

    添加一个属性list来存放color

    1. private ArrayList<Integer> mColorList;
    1. mColorList = new ArrayList<>();
    2. mColorList.add(Color.parseColor("#33B5E5"));
    3. mColorList.add(Color.parseColor("#AA66CC"));
    4. mColorList.add(Color.parseColor("#99CC00"));
    5. mColorList.add(Color.parseColor("#FFBB33"));
    6. mColorList.add(Color.parseColor("#FF4444"));

    在循环中,取出颜色值

    1. for (int j = 0; j < mLineCount; j++) {
    2. mPaint.setColor(mColorList.get(j));

    效果如下:

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

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

    先给每个蜂窝编号

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

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

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

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

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

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

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

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

    1. mTextList = new ArrayList<>();
    2. for(int i =0;i<mLineCount*mColumnsCount;i++){
    3. mTextList.add("wing "+i);
    4. }
    5. mTextPaint = new Paint();
    6. mTextPaint.setTextSize(20);

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

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

    下面是全部的ondraw

    1. @Override
    2. protected void onDraw(Canvas canvas) {
    3. //正六边形的高
    4. float height = (float) (Math.sqrt(3) * mLength);
    5. for (int j = 0; j < mLineCount; j++) {
    6. mPaint.setColor(mColorList.get(j));
    7. if (j % 2 == 0) {
    8. // mPaint.setColor(Color.parseColor("#FFBB33"));
    9. for (int i = 0; i < mColumnsCount; i++) {
    10. int txtId = j*3 +i;
    11. //横坐标偏移量
    12. float offset = mLength * 3 * i;
    13. //左上角的x
    14. float x = mLength / 2 + offset;
    15. float y = j * height / 2;
    16. mPath.reset();
    17. getPath(height, x, y);
    18. canvas.drawPath(mPath, mPaint);
    19. float txtLength = mTextPaint.measureText(mTextList.get(txtId));
    20. canvas.drawText(mTextList.get(txtId),x+mLength/2-txtLength/2,y+height/2+5, mTextPaint);
    21. }
    22. } else {
    23. // mPaint.setColor(Color.parseColor("#AA66CC"));
    24. for (int i = 0; i < mColumnsCount; i++) {
    25. int txtId = j*3 +i;
    26. float offset = mLength * 3 * i;
    27. float x = mLength * 2 + offset;
    28. float y = (height / 2) * j;
    29. mPath.reset();
    30. getPath(height, x, y);
    31. canvas.drawPath(mPath, mPaint);
    32. float txtLength = mTextPaint.measureText(mTextList.get(txtId));
    33. canvas.drawText(mTextList.get(txtId),x+mLength/2-txtLength/2,y+height/2+5, mTextPaint);
    34. }
    35. }
    36. }
    37. }

    现在的效果图如下:

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

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

    1. /**
    2. * 设置列数
    3. * @param mColumnsCount
    4. */
    5. public void setColumnsCount(int mColumnsCount) {
    6. this.mColumnsCount = mColumnsCount;
    7. invalidate();
    8. }
    9. /**
    10. * 设置行数
    11. * @param mLineCount
    12. */
    13. public void setLineCount(int mLineCount) {
    14. this.mLineCount = mLineCount;
    15. invalidate();
    16. }
    17. /**
    18. * 设置文本数据
    19. */
    20. public void setTextList(ArrayList<String> textList) {
    21. mTextList.clear();
    22. mTextList.addAll(textList);
    23. invalidate();
    24. }
    25. /**
    26. * 设置颜色数据
    27. * @param colorList
    28. */
    29. public void setColorList(ArrayList<Integer> colorList) {
    30. mColorList.clear();
    31. mColorList.addAll(colorList);
    32. invalidate();
    33. }

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

    1. @Override
    2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    3. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    4. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    5. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    6. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    7. if(widthMode == MeasureSpec.AT_MOST){
    8. widthSize = (int) ((3f*mColumnsCount+0.5f) *mLength);
    9. }else{
    10. // throw new IllegalStateException("only support wrap_content");
    11. }
    12. if(heightMode == MeasureSpec.AT_MOST){
    13. heightSize = (int) ((mLineCount/2f +0.5f) * (Math.sqrt(3) * mLength));
    14. }else{
    15. // throw new IllegalStateException("only support wrap_content");
    16. }
    17. setMeasuredDimension(widthSize,heightSize);
    18. }
    19.  

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

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

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

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

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

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