Android 自定义雷达图(蜘蛛网图)
来源:互联网 发布:sql怎么去掉重复值 编辑:程序博客网 时间:2024/04/28 03:09
这次自定义实现雷达图,它可以用在分析某些内容所占的比例,比较直观地突出某些数据,比如可以用在游戏玩家的各项能力的分析上,那么它的各项指标就比较明显地看出来了。效果图如下:
看完这幅图大家就清楚要实现的内容吧。下面来实现它。
一、思路
1、先画背景的正六边形。
(1)可以看到每一部分三角形都是相同的,那么我们可以先画其中一部分的三角形,剩下的就重复操作就行了。
就是上面红色的三角形部分(画的有的丑,这不是重点)。如果单独画这部分内容,相信大家都会有自己的想法了。可以看到图中同一个顶点(原点)有5个三角形,我一开始的想法是从最小那个三角形画起,然后重复的操作画剩余的三角形,这当然可以画出来,但是有个问题就是它们的线重复了,就是画完最小三角形之后画第二个三角形的时候,它的边在次经过上一个三角形的边,因此上一个三角形的边和后面三角形的边重复部分就会比较粗,这就不符合我们的需求了。因此我重新想另外一个方法,就是先画顶点为原点那两条最大三角形的边,然后把每一个三角形的最后一条边分别画上去,那样三角形的每一条边都没有重复了。
(2)画剩余的三角形,让它们组合成正六边形。原理就是让画布旋6次,把每次画的结果都保存下来就可以组合成正六边形了,具体看后面的代码。
2、画文字,我这里是逆时针画文字的,就是“个人”,“团队”这样顺序。
3、画各项能力值所组成的图形,并把能力值以点形式画出来。
二、代码实现
说了那么多,终于要上代码了。
1、自定义控件的一些属性。
在res/values/目录下新建attrs.xml文件。然后就写上自己要定义的属性。这里就定义了几个简单的属性,用户可以自己添加。
<!-- 蜘蛛网图 --> <declare-styleable name="MyNetPic"> <attr name="lineColor" format="color"/><!-- 线的颜色 --> <attr name="cotentColor" format="color"/><!-- 图形的颜色 --> <attr name="side" format="dimension"/> <!-- 三角形边长 --> <attr name="distance" format="dimension"/> <!-- 当前三角形和上一个三角形的距离 --> <attr name="number" format="integer"/><!-- 三角形的数量 --> </declare-styleable>
2、代码中获取自定义属性的值。
获取完之后记得recycle,具体可以看以下的代码。在构造方法里调用这个init方法即可。
// 默认的颜色值 private final int green = 0xaf93d150; private final int blue = 0xff4aadff; private final int white = 0xffffffff; private final int black = 0xff000000; // 自定义的属性值 private int lineColor;// 线的颜色 private int contentColor;// 图形的内部的颜色 private float side;// 三角形的边长 private float distance;// 当前三角形和上一个三角形的距离 private int num;// 三角形的数量private void init(Context context, AttributeSet attrs) { this.context = context; // 获取自定义属性的值 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyNetPic); lineColor = a.getColor(R.styleable.MyNetPic_lineColor, blue); contentColor = a.getColor(R.styleable.MyNetPic_cotentColor, green); side = a.getDimension(R.styleable.MyNetPic_side, 25); distance = a.getDimension(R.styleable.MyNetPic_distance, 25); num = a.getInteger(R.styleable.MyNetPic_number, 5); a.recycle(); paint = new Paint(); textPaint = new Paint(); contentPaint = new Paint(); // 把dp转换为px side = dip2px(context, side); distance = dip2px(context, distance); textDistance = dip2px(context, textDistance); textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSize, getResources().getDisplayMetrics()); drawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); texts = new String[] { "个人", "团队", "意识", "领悟", "思维", "敏捷" }; abilitys = new float[] { 150, 145, 130, 160, 120, 105 }; }
3、重写onMeasure方法。
处理为wrap_content情况,那么它的specMode是AT_MOST模式,在这种模式下它的宽/高等于spectSize,这种情况下view的spectSize是parentSize,而parentSize是父容器目前可以使用大小,就是父容器当前剩余的空间大小, 就相当于使用match_parent一样 的效果,因此我们可以设置一个默认的值。我这里设置默认的宽高都是200。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpectMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpectSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpectMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpectSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpectMode == MeasureSpec.AT_MOST && heightSpectMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthSpectMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, heightSpectSize); } else if (heightSpectMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpectSize, mHeight); } }
4、在onlayout里获取控件的宽高。
@Override protected void onLayout(boolean changed, int left, int top, int right,int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed) { mWidth = right - left; mHeight = bottom - top; } }
5、在onDraw方法实现图形的绘制。
@Override protected void onDraw(Canvas canvas) { // 从canvas层面去除绘制时锯齿 canvas.setDrawFilter(drawFilter); // 移到区域的中心 canvas.translate(mWidth / 2, mHeight / 2); // 将y轴翻转 // canvas.scale(1f, -1f); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(1f); drawBackGroundPic(canvas); drawMyText(canvas); drawContent(canvas); }
6、画背景的正六边形。
使用canvas.save();来保存上一次的图层,在新的图层里画其它部分的三角形, 最后用canvas.restore();把新的图层添加到原来的图层上。
/** * 画作为背景的正六边形 * * @param canvas */ private void drawBackGroundPic(Canvas canvas) { paint.setAntiAlias(true); paint.setColor(lineColor); // 先画三角形 Path path = new Path(); float x2, x3; int AngleCount = 6; float xArray[] = new float[num];// 存储x坐标 float yArray[] = new float[num];// 存储y坐标 for (int j = 0; j < AngleCount; j++) { canvas.save(); canvas.rotate(j * 60); // 计算每个三角形第三个点的坐标 for (int i = 0; i < num; i++) { x2 = side + i * distance;// 第二个点 xArray[i] = x3 = x2 / 2.0f;// 第三个点的横坐标,因为cos60=1/2; // 用勾股定理计算第三个点的y坐标 yArray[i] = -(float) Math.sqrt(x2 * x2 - x3 * x3); } // 先画最大那个三角形的两条边 path.moveTo(0, 0); path.lineTo(side + (num - 1) * distance, 0); path.moveTo(0, 0); path.lineTo(xArray[num - 1], yArray[num - 1]); // 再画每个三角形的第三条边 for (int i = 0; i < num; i++) { path.moveTo(xArray[i], yArray[i]); path.lineTo(side + i * distance, 0); } canvas.drawPath(path, paint); canvas.restore(); } }
7、逆时针方向画文字。
我这里是逆时针画文字的,就是“个人”,“团队”这样顺序。要注意的是使用正余弦函数的时候要转换一下不是直接拿角度就用,如Math.cos(60.0 * Math.PI / 180)。可以用Rect textRect = new Rect();textPaint.getTextBounds(texts[0], 0,texts[0].length(), textRect);方法来获取文字的宽高。
/** * 逆时针画文字,最右边的为第一个 * * @param canvas */ private void drawMyText(Canvas canvas) { textPaint.setColor(black); textPaint.setTextSize(textSize); // 文字距离原点的大小,为最大的三角形边长+文字距离三角形的大小 float d = side + (num - 1) * distance + textDistance; // 因为图形是对称的,所以直接计算其中一个角度的坐标,之后就可以重复使用了 float dx = (float) (d * Math.cos(60.0 * Math.PI / 180)); float dy = (float) (d * Math.sin(60.0 * Math.PI / 180)); Rect textRect = new Rect(); textPaint.getTextBounds(texts[0], 0, texts[0].length(), textRect); canvas.drawText(texts[0], d, textRect.height() / 2, textPaint); canvas.drawText(texts[1], dx, -dy, textPaint); canvas.drawText(texts[2], -dx - textRect.width(), -dy, textPaint); canvas.drawText(texts[3], -d - textRect.width(), textRect.height() / 2,textPaint); canvas.drawText(texts[4], -dx - textRect.width(), dy + textRect.height(), textPaint); canvas.drawText(texts[5], dx, dy + textRect.height(), textPaint); }
8、画能力值形成的多边形图形。
要注意的地方是颜色要有一定的透明度,才能够看到底部背景正六边形,这里就设置为private final int green = 0xaf93d150;这颜色。用户需要自己设置6个能力所对应的值,然后计算每个值对应(x,y)坐标,最后用path类把它们连起来,同时画出这6个点。
/**画能力值形成的图形 * @param canvas */ private void drawContent(Canvas canvas) { contentPaint.setColor(contentColor); float d =side + (num - 1) * distance; //用两个数组来保存6个点的坐标 float xArray[] = new float[abilitys.length]; float yArray[] = new float[abilitys.length]; int count = abilitys.length; //计算6个能力值的x,y坐标 for (int i = 0; i < count; i++) { float conX = (float) (Math.cos(i * 60.0 * Math.PI / 180)); float conY = (float) (Math.sin(i * 60.0 * Math.PI / 180)); // 为了防止能力值比最大的三角形的边长还要大,这里就求余 xArray[i] = abilitys[i] % d * conX; yArray[i] = -abilitys[i] % d * conY; } //画图形 Path path = new Path(); path.moveTo(xArray[0], yArray[0]); for(int i=1;i<count;i++){ path.lineTo(xArray[i], yArray[i]); } path.close(); //画6个顶点 canvas.drawPath(path, contentPaint); contentPaint.setColor(black); for(int i=0;i<count;i++){ canvas.drawCircle(xArray[i], yArray[i],dip2px(context, 3),contentPaint); } }
9、在布局里使用。
要使用自定义的属性,则要在根节点里添加xmlns:app=”http://schemas.android.com/apk/res-auto”, 这里的”app”是可以随便定义的,但是在控件里使用自定义属性的时候它的前缀要和这里一样。
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="@android:color/white" >
<com.example.test22.view.NetPicture android:id="@+id/myNetPic" android:layout_width="match_parent" android:layout_height="match_parent" app:lineColor="@android:color/holo_blue_bright"/>
10、使用Builder封装。
到上述的步骤这控件应经可以使用了,但是为了更好的调用,还是简单的进行封装一下,对外提供一些方法。
public void show(){ postInvalidate(); } public static class NetPicBuilder { private static NetPicture netPicture; private static NetPicBuilder netPicBuilder; private NetPicBuilder(){ } public static NetPicBuilder createBuilder(NetPicture netPic){ netPicture = netPic; synchronized (NetPicBuilder.class) { if(netPicBuilder==null){ netPicBuilder = new NetPicBuilder(); } } return netPicBuilder; } /**设置文本的内容 * @param s * @return */ public static NetPicBuilder setTextContent(String[] s){ netPicture.setTexts(s); return netPicBuilder; } /**设置能力值 * @param ab * @return */ public static NetPicBuilder setAbilitys(float[] ab){ netPicture.setAbilitys(ab); return netPicBuilder; } /** * 把图形显示出来 */ public static void show(){ if(netPicture==null){ throw new NullPointerException("NetPicBuilder is null"); } netPicture.show(); } }
11、一些公共的方法。
/** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) */ public int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); }
总结
这控件比较适合自己练习,所以就自己动手去实现一下,虽然不是什么高大上的控件,但是每一个控件的实现都能让自己有所收获的,进步一点点就是最大的收获了。
源码下载
- Android 自定义雷达图(蜘蛛网图)
- Android蜘蛛网图/雷达图
- Android雷达图(蜘蛛网图)绘制
- <Android学习日志>自定义View-正五边形(蜘蛛网雷达效果)
- Android自定义View-蜘蛛网属性图(五边形图)
- 自定义蜘蛛网图 NetView
- 自定义View -- 蜘蛛网图
- JFreeChart画雷达图、带刻度雷达图、蜘蛛网、带刻度蜘蛛网
- Android 自定义雷达图
- JFreeChart画雷达图、带刻度雷达图、蜘蛛网、带刻度蜘蛛网(转自:http://blog.csdn.net/guoquanyou/archive/2008/12/10/3488313.aspx)
- 使用JFreeChart生成带刻度的雷达图(蜘蛛网图)
- Android实现蜘蛛网图绘制
- 自定义雷达图(CustomRadarCharView)
- Android 自定义View练习:雷达图(比重)绘制
- android雷达图
- Android 雷达图
- Android 雷达图
- [UI]自定义View--雷达图
- 第四周项目三求并联电阻的阻值
- CPP.Freshman Vol.2 C++面向对象程序设计——类
- wiki上一个比较好的HMM例子
- 空间申请问题
- Apache与Nginx的优缺点比较
- Android 自定义雷达图(蜘蛛网图)
- 每一本书都要精度
- SharedPreferences工具类
- Python标准库os.path包、glob包使用实例
- iCow player
- 硬盘存储性能与存取方式和存储介质的关系
- stm32F105的can2问题
- php从mysql中读取空间数据在javascript中调用这个空间数据的值
- 通过源码,手把手带你学属性动画(四) - 理解插值器(附神器)