Android雷达图(蜘蛛网图)
简介
最近因为项目的需要,需要实现雷达图来展示各科目的对题率。
雷达图的绘制不算复杂,只要按照一定流程来绘制就可以了,其中使用的最多的是path路径类,使用这个类便于我们绘制出多边形等效果。
效果图如下:
使用方式
使用方式很简单,直接在布局文件里面使用这个控件,记得设置一个合适的大小就可以。
当然也有开放一些public方法,可以进行数据、文本颜色等设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /** * 设置数据 * @param points */ public void setData(ArrayList<LastPoint> points){} /** * 设置文本 * @param titles */ public void setTitles(String[] titles){} /** * 设置圈数 * @param count */ public void setCount( int count){} /** * 设置网格线颜色 * @param color */ public void setLineColor( int color){} /** * 设置填充区域颜色 * @param color */ public void setValueColor( int color){} /** * 设置文本颜色 * @param color */ public void setTextColor( int color){} |
具体实现
一般自定义控件的流程有以下几个步骤(个人观点):
* 1、构造函数(初始化)
* 2、onMeasure(测量大小)
* 3、onSizeChanged(确定大小)
* 4、onLayout(子view的位置,如果包含子view的话)
* 5、onDraw(绘制内容)
* 6、暴露给外部的接口
在该控件中,2、4都不用考虑,只要确定了大小(onSizeChanged),就能够计算出整个布局的中心,雷达图是以这个中心开始绘制的。
1 2 3 4 5 6 7 8 9 10 | @Override protected void onSizeChanged( int w, int h, int oldw, int oldh) { super .onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; mCenterX = mWidth/ 2 ; mCenterY = mHeight/ 2 ; mRadius = (Math.min(mWidth,mHeight)/ 2 * 0 .9f); postInvalidate(); } |
绘制蜘蛛网图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /** * 画网格 * @param canvas */ private void drawLine(Canvas canvas){ Path path = new Path(); //网格线之间的间距 float distance = mRadius / (mCount- 1 ); for ( int i = 0 ; i < mCount; i++){ //外面的网格图形 float currentRadius = i * distance; //当前半径 if (i == mCount - 1 ){ //存储最后一圈网格的点的坐标 mLastPoints.add( new LastPoint(currentRadius, 0 )); mLastPoints.add( new LastPoint(currentRadius/ 2 ,-currentRadius)); mLastPoints.add( new LastPoint(-currentRadius/ 2 ,-currentRadius)); mLastPoints.add( new LastPoint(-currentRadius, 0 )); mLastPoints.add( new LastPoint(-currentRadius/ 2 ,currentRadius)); mLastPoints.add( new LastPoint(currentRadius/ 2 ,currentRadius)); } //6个点坐标组成一个网格图形 path.lineTo(currentRadius, 0 ); //设置上一次操作的坐标点 path.moveTo(currentRadius, 0 ); path.lineTo(currentRadius/ 2 ,-currentRadius); path.lineTo(-currentRadius/ 2 ,-currentRadius); path.lineTo(-currentRadius, 0 ); path.lineTo(-currentRadius/ 2 ,currentRadius); path.lineTo(currentRadius/ 2 ,currentRadius); path.close(); canvas.drawPath(path,mLinePaint); } } |
绘制从中心到末端的直线
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * 画网格对角线 * @param canvas */ private void drawGridLine(Canvas canvas){ Path path = new Path(); for ( int i = 0 ; i < mLastPoints.size(); i++){ path.reset(); LastPoint point = mLastPoints.get(i); float x = point.x; float y = point.y; path.lineTo(x, y); canvas.drawPath(path, mLinePaint); } } |
绘制末端文本
由于文本与末端有一定的距离,所以需要加上一定的偏移量;当文本在网格左边显示的时候,会与网格有重叠,所以需要先计算文本长度,然后再向左边偏移对应的距离,这样就可以解决重叠问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /** * 画文本 * @param canvas */ private void drawText(Canvas canvas){ for ( int i = 0 ; i < mLastPoints.size(); i++){ //文本长度 float dis = mTextPaint.measureText(mTitles[i]); LastPoint point = mLastPoints.get(i); float x = point.x; float y = point.y; if (i == 2 || i == 3 || i == 4 ){ //左边绘制文本:文本显示在坐标左边 x = x - dis; } if (y > 0 ){ y+= 18 ; } canvas.drawText(mTitles[i],x,y,mTextPaint); } } |
绘制填充区域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * 画数据线:填充区域 * @param canvas */ private void drawDataLine(Canvas canvas){ if (mDataPoints == null || mDataPoints.size() == 0 ) return ; Path path = new Path(); for ( int i = 0 ; i < mDataPoints.size(); i++){ LastPoint point = mDataPoints.get(i); float x = point.x; float y = point.y; path.lineTo(x, y); if (i == 0 ){ //将上一次操作点移到第一个点坐标,保证最后调用close,形成一个封闭的形状 path.moveTo(x,y); } mValuePaint.setAlpha( 255 ); //画小圆点 canvas.drawCircle(x,y, 8 ,mValuePaint); } path.close(); mValuePaint.setAlpha( 127 ); canvas.drawPath(path, mValuePaint); } |
最后,贴上完整的源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | package com.ha.cjy.myproject.view.widget; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import java.util.ArrayList; /** * 自定义雷达图 * 1、构造函数(初始化) * 2、onMeasure(测量大小) * 3、onSizeChanged(确定大小) * 4、onLayout(子view的位置,如果包含子view的话) * 5、onDraw(绘制内容) * 6、暴露给外部的接口 * Created by cjy on 17/8/15. */ public class CustomRadarView extends View { //宽度 private int mWidth; //高度 private int mHeight; //原点坐标 private int mCenterX; private int mCenterY; //网格半径 private float mRadius; //网格圈数 private int mCount = 10 ; //Paint private Paint mLinePaint; private Paint mValuePaint; private Paint mTextPaint; //颜色值 private int mLineColor = Color.GRAY; private int mValueColor = Color.BLUE; private int mTextColor = Color.BLACK; //最后一圈网格坐标点集合 private ArrayList<LastPoint> mLastPoints = new ArrayList<LastPoint>(); //数据坐标点集合 private ArrayList<LastPoint> mDataPoints = new ArrayList<LastPoint>(); //文本集合 private String[] mTitles = new String[]{ "科目A" , "科目A1" , "科目1" , "科目D" , "科目E" , "科目F" }; public CustomRadarView(Context context) { super (context); init(context); } public CustomRadarView(Context context, @Nullable AttributeSet attrs) { super (context, attrs); init(context); } public CustomRadarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); init(context); } @Override protected void onSizeChanged( int w, int h, int oldw, int oldh) { super .onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; //中心坐标 mCenterX = mWidth/ 2 ; mCenterY = mHeight/ 2 ; mRadius = (Math.min(mWidth,mHeight)/ 2 * 0 .9f); postInvalidate(); } private void init(Context context){ mLinePaint = new Paint(); mLinePaint.setStyle(Paint.Style.STROKE); mLinePaint.setStrokeWidth( 2 ); mLinePaint.setColor(mLineColor); mValuePaint = new Paint(); mValuePaint.setStyle(Paint.Style.FILL_AND_STROKE); mValuePaint.setStrokeWidth( 2 ); mValuePaint.setColor(mValueColor); mTextPaint = new Paint(); mTextPaint.setStyle(Paint.Style.STROKE); mTextPaint.setStrokeWidth( 2 ); mTextPaint.setColor(mTextColor); mTextPaint.setTextSize( 28 ); mTextPaint.setAntiAlias( true ); } @Override protected void onDraw(Canvas canvas) { super .onDraw(canvas); //移动原点坐标 canvas.translate(mCenterX,mCenterY); //画网格线 drawLine(canvas); //画网格对角线 drawGridLine(canvas); //画文本 drawText(canvas); //画数据线 drawDataLine(canvas); } /** * 画网格 * @param canvas */ private void drawLine(Canvas canvas){ Path path = new Path(); //网格线之间的间距 float distance = mRadius / (mCount- 1 ); for ( int i = 0 ; i < mCount; i++){ //外面的网格图形 float currentRadius = i * distance; //当前半径 if (i == mCount - 1 ){ //存储最后一圈网格的点的坐标 mLastPoints.add( new LastPoint(currentRadius, 0 )); mLastPoints.add( new LastPoint(currentRadius/ 2 ,-currentRadius)); mLastPoints.add( new LastPoint(-currentRadius/ 2 ,-currentRadius)); mLastPoints.add( new LastPoint(-currentRadius, 0 )); mLastPoints.add( new LastPoint(-currentRadius/ 2 ,currentRadius)); mLastPoints.add( new LastPoint(currentRadius/ 2 ,currentRadius)); } //6个点坐标组成一个网格图形 path.lineTo(currentRadius, 0 ); //设置上一次操作的坐标点 path.moveTo(currentRadius, 0 ); path.lineTo(currentRadius/ 2 ,-currentRadius); path.lineTo(-currentRadius/ 2 ,-currentRadius); path.lineTo(-currentRadius, 0 ); path.lineTo(-currentRadius/ 2 ,currentRadius); path.lineTo(currentRadius/ 2 ,currentRadius); path.close(); canvas.drawPath(path,mLinePaint); } } /** * 画网格对角线 * @param canvas */ private void drawGridLine(Canvas canvas){ Path path = new Path(); for ( int i = 0 ; i < mLastPoints.size(); i++){ path.reset(); LastPoint point = mLastPoints.get(i); float x = point.x; float y = point.y; path.lineTo(x, y); canvas.drawPath(path, mLinePaint); } } /** * 画文本 * @param canvas */ private void drawText(Canvas canvas){ for ( int i = 0 ; i < mLastPoints.size(); i++){ //文本长度 float dis = mTextPaint.measureText(mTitles[i]); LastPoint point = mLastPoints.get(i); float x = point.x; float y = point.y; if (i == 2 || i == 3 || i == 4 ){ //左边绘制文本:文本显示在坐标左边 x = x - dis; } if (y > 0 ){ y+= 18 ; } canvas.drawText(mTitles[i],x,y,mTextPaint); } } /** * 画数据线:填充区域 * @param canvas */ private void drawDataLine(Canvas canvas){ if (mDataPoints == null || mDataPoints.size() == 0 ) return ; Path path = new Path(); for ( int i = 0 ; i < mDataPoints.size(); i++){ LastPoint point = mDataPoints.get(i); float x = point.x; float y = point.y; path.lineTo(x, y); if (i == 0 ){ //将上一次操作点移到第一个点坐标,保证最后调用close,形成一个封闭的形状 path.moveTo(x,y); } mValuePaint.setAlpha( 255 ); //画小圆点 canvas.drawCircle(x,y, 8 ,mValuePaint); } path.close(); mValuePaint.setAlpha( 127 ); canvas.drawPath(path, mValuePaint); } /** * 设置数据 * @param points */ public void setData(ArrayList<LastPoint> points){ mDataPoints = points; invalidate(); } /** * 设置文本 * @param titles */ public void setTitles(String[] titles){ mTitles = titles; invalidate(); } /** * 设置圈数 * @param count */ public void setCount( int count){ mCount = count; invalidate(); } /** * 设置网格线颜色 * @param color */ public void setLineColor( int color){ mLineColor = color; mLinePaint.setColor(mLineColor); invalidate(); } /** * 设置填充区域颜色 * @param color */ public void setValueColor( int color){ mValueColor = color; mValuePaint.setColor(mValueColor); invalidate(); } /** * 设置文本颜色 * @param color */ public void setTextColor( int color){ mTextColor = color; mTextPaint.setColor(mTextColor); invalidate(); } /** * 坐标点 */ public static class LastPoint { private float x; private float y; public LastPoint( float x, float y) { this .x = x; this .y = y; } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步