手把手带你画一个漂亮蜂窝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的时候在,分开编号,按照编号进行回调即可,这个问题,准备下篇博客解决,请大家继续关注我的博客 蟹蟹!。
本项目地址:点击打开链接
如果你觉得我写的还不错,请点击一下顶,继续关注我。谢谢!!
手把手带你画一个漂亮蜂窝view Android自定义view的更多相关文章
- 一个漂亮而强大的自定义view
代码地址如下:http://www.demodashi.com/demo/13502.html 简介 主要提供一个漂亮而强大的自定义SeekBar,进度变化由提示牌 (sign)展示,具有强大的属性设 ...
- 手把手带你画一个动态错误提示 Android自定义view
嗯..再差1篇就可以获得持之以恒徽章了,今天带大家画一个比较简单的view. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/504771 ...
- 手把手带你做一个超炫酷loading成功动画view Android自定义view
写在前面: 本篇可能是手把手自定义view系列最后一篇了,实际上我也是一周前才开始真正接触自定义view,通过这一周的练习,基本上已经熟练自定义view,能够应对一般的view需要,那么就以本篇来结尾 ...
- 手把手带你画一个 时尚仪表盘 Android 自定义View
拿到美工效果图,咱们程序员就得画得一模一样. 为了不被老板喷,只能多练啊. 听说你觉得前面几篇都so easy,那今天就带你做个相对比较复杂的. 转载请注明出处:http://blog.csdn.ne ...
- 手把手带你画一个 时尚仪表盘 Android 自己定义View
拿到美工效果图.咱们程序猿就得画得一模一样. 为了不被老板喷,仅仅能多练啊. 听说你认为前面几篇都so easy,那今天就带你做个相对照较复杂的. 转载请注明出处:http://blog.csdn.n ...
- 手把手教你打造一个心电图效果View Android自定义View
大家好,看我像不像蘑菇-因为我在学校呆的发霉了. 思而不学则殆 丽丽说得对,我有奇怪的疑问,大都是思而不学造成的,在我书读不够的情况下想太多,大多等于白想,所以革命没成功,同志仍需努力. 好了废话不说 ...
- 简单说说Android自定义view学习推荐的方式
这几天比较受关注,挺开心的,嘿嘿. 这里给大家总结一下学习自定义view的一些技巧. 以后写自定义view可能不会写博客了,但是可以开源的我会把源码丢到github上我的地址:https://git ...
- 手把手教你画一个 逼格满满圆形水波纹loadingview Android
才没有完结呢o( ̄︶ ̄)n .大家好,这里是番外篇. 拜读了爱哥的博客,又学到不少东西.爱哥曾经说过: 要站在巨人的丁丁上. 那么今天,我们就站在爱哥的丁丁上来学习制作一款自定义view(开个玩笑,爱 ...
- Android自定义view(一):制作一个最最最简单的自定义view
转载:https://blog.csdn.net/wsyizmao/article/details/78491422 浅谈安卓自定义view(一):制作一个最最最简单的自定义view 对于安卓程序员来 ...
随机推荐
- formData的实现
参考:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest <!doctype ...
- 制定一个apk路径 然后跳出安装界面
制定一个apk的路径 然后跳出界面让用户选择是否安装 我们系统有一个写好的Activity来协助我们完成这一功能 我们来看看它的清单文件 <?xml version="1.0" ...
- 豌豆夹Redis解决方案Codis源码剖析:Dashboard
豌豆夹Redis解决方案Codis源码剖析:Dashboard 1.不只是Dashboard 虽然名字叫Dashboard,但它在Codis中的作用却不可小觑.它不仅仅是Dashboard管理页面,更 ...
- 给定一个实数数组,按序排列(从小到大),从数组从找出若干个数,使得这若干个数的和与M最为接近,描述一个算法,并给出算法的复杂度。
有N个正实数(注意是实数,大小升序排列) x1 , x2 ... xN,另有一个实数M. 需要选出若干个x,使这几个x的和与 M 最接近. 请描述实现算法,并指出算法复杂度. #define M 8 ...
- FORM触发器
FORM级触发器 PRE-FORM该触发器是在用户双击功能后,进入form前 WHEN-NEW-FORM-INSTANCE该触发器是在用户一进入form时执行 WHEN-FORM-NAVIGAT ...
- 【问题汇总】ScrollView嵌套ListView的问题
因产品的需求,需要在ScrollView中嵌套ListView来达到效果.众所周知,ScrollVIew和ListView都是可滑动的容器,嵌套使用一定会出现一些问题. [html] view pla ...
- Servlet之文件上传
上传表单中的注意事项: 表单 method 属性应该设置为 POST 方法,不能使用 GET 方法 表单 enctype 属性应该设置为multipart/form-data 下面的实例是借助于com ...
- [python] 带有参数并且传递参数的装饰器
场景时这样的,我有个一大堆任务,我要给这些任务计时,入库.就需要一个带有参数的装饰器来记录任务名称, 在任务执行前和执行之后都需要记录任务当时执行的时刻. #-*- encoding=utf-8 -* ...
- 文件操作(File类等)API摘要
Console 此类包含多个方法,可访问与当前 Java 虚拟机关联的基于字符的控制台设备(如果有). 虚拟机是否具有控制台取决于底层平台,还取决于调用虚拟机的方式.如果虚拟机从一个交互式命令行开始启 ...
- Spark 1.0 开发环境构建:maven/sbt/idea
因为我原来对maven和sbt都不熟悉,因此使用两种方法都编译了一下.下面记录一下编译时候遇到的问题.然后介绍一下如果使用IntelliJ IDEA 13.1构建开发环境. 首先准备java环境和sc ...