内容简介:最近一直在学习先看效果图需要解决的问题
最近一直在学习 Flutter
,感觉还不错,但是 Android
也不能拉下,回顾下前3篇的内容,让我们一起画个雷达图吧。
- 【Android自定义View】绘图之基础篇(一)
- 【Android自定义View】绘图之Path篇(二)
- 【Android自定义View】绘图之文字篇(三)
- 【Android自定义View】绘图之实战篇(雷达图)(四)
先看效果图
分析
需要解决的问题
- 正N边形的绘制
- 虚线的绘制
- 间隔线的绘制
- 文字位置计算
- 数值坐标计算
解决问题
- 正N边形的绘制 首先,我们以屏幕中心点O (
centerX, centerY
)正上方的点A为起始点,则其坐标为centerX, centerY - radius
, 则B点的坐标应该为centerX - radius * Math.sin(∠AOB)
,centerY + radius - radius * Math.cos(∠AOB)
/** * arc为弧度,在顶点处建立直角坐标系,用r和arc确定下一个点的坐标 */ public Point nextPoint(Point point, double arc, int radius) { Point p = new Point(); p.x = (int) (point.x - radius * Math.sin(arc)); p.y = (int) (point.y + radius - radius * Math.cos(arc)); return p; } 复制代码
请注意,此处的角度在 java 中要转为弧度, sideSize
为N边
/** * 角度制转弧度制 */ private double degree2radian() { return 2 * Math.PI / sideSize; } 复制代码
所以,此时,边框的 Path
应该为: Path
相关内容可查看 【Android自定义View】绘图之Path篇(二)
/** * 返回边框的path * * @param sPoint (centerX, centerY - radiu) * @param count * @param radius * @return */ private Path makePath(Point sPoint, int count, int radius) { Path path = new Path(); path.moveTo(sPoint.x, sPoint.y); for (int i = 1; i < count; i++) { Point point = nextPoint(sPoint, -degree2radian() * i, radius); path.lineTo(point.x, point.y); } path.close(); return path; } 复制代码
- 虚线 可以使用
setPathEffect
来设置
Paint painte = new Paint(); painte.setStrokeWidth(lineWidth); painte.setColor(cutlineColor); painte.setStyle(Paint.Style.STROKE); painte.setPathEffect(new DashPathEffect(new float[]{10, 10}, 0)); 复制代码
绘制的话,需要将上一步计算的坐标,每个和中心点相连即可
for (int i = 0; i < sideSize; i++) { Point point = nextPoint(sPoint, -degree2radian() * i, radius); Path path = new Path(); path.moveTo(centerX, centerY); path.lineTo(point.x, point.y); //绘制分割线 canvas.drawPath(path, painte); //计算文字位置使用 pointList.add(point); } 复制代码
- 间隔线的绘制 间隔线的处理跟边框的绘制相似,只是传入的半径不同
for (int i = 0; i < spaceCount; i++) { int radiu = radius / spaceCount * (i + 1); Path p = makePath(new Point(centerX, centerY - radiu), sideSize, radiu); paint.setColor(boxlineColor); canvas.drawPath(p, paint); } 复制代码
- 文字位置计算 文字的位置计算,需要用到上一步保存的顶点坐标
for (int i = 0; i < pointList.size(); i++) { if (labelText != null && labelText.size() > 0) { //绘制顶点文字 drawTextTop(pointList.get(i), labelText.get(i)); } } 复制代码
绘制文字,这里涉及到的在上一篇中有详细描述,详情可查看 【Android自定义View】绘图之文字篇(三)
/** * 绘制顶点文字 * * @param point * @param text */ private void drawTextTop(Point point, String text) { Rect rect = new Rect(); paint.getTextBounds(text, 0, text.length(), rect); int x; int y; //偏移处理 if (point.x - centerX == 0 || Math.abs(point.x - centerX) < 5) { x = point.x; } else if (point.x - centerX > textSpace) { x = point.x + textSpace; } else { x = point.x - textSpace; } if (point.y - centerY == 0 || Math.abs(point.y - centerY) < 5) { y = point.y; } else if (point.y - centerY > textSpace) { y = point.y + textSpace; } else { y = point.y - textSpace; } paint.setStyle(Paint.Style.FILL); paint.setColor(textColor); canvas.drawText(text, x, y + (rect.bottom - rect.top) / 2, paint); paint.setStyle(Paint.Style.STROKE); } 复制代码
- 数值坐标计算 数值坐标计算相对麻烦点,通过中心点正上方的坐标来推导旋转后的坐标
Path valuePath = makePath(radius, labelValue); paint.setColor(valueColor); paint.setStyle(Paint.Style.FILL); canvas.drawPath(valuePath, paint); 复制代码
private Path makePath(int radius, List<Double> values) { Path path = new Path(); Point sPoint = new Point(centerX, (int) (centerY - radius * values.get(0))); path.moveTo(sPoint.x, sPoint.y); for (int i = 1; i < values.size(); i++) { sPoint = new Point(centerX, (int) (centerY - radius * values.get(i))); Point point = nextPoint(sPoint, -degree2radian() * i, (int) (radius * values.get(i))); path.lineTo(point.x, point.y); } path.close(); return path; } 复制代码
最后,在加上一些自定义属性,一个雷达图就做好了
<resources> <declare-styleable name="RadarView"> <!--线颜色--> <attr name="ch_boxlineColor" format="color" /> <!--文字颜色--> <attr name="ch_textColor" format="color" /> <!--分割线颜色--> <attr name="ch_cutlineColor" format="color" /> <!--内容颜色--> <attr name="ch_valueColor" format="color" /> <!--线宽--> <attr name="ch_lineWidth" format="dimension" /> <!--文字大小--> <attr name="ch_textSize" format="dimension" /> <!--几边形--> <attr name="ch_sideSize" format="integer" /> <!--辅助线--> <attr name="ch_spaceCount" format="integer" /> <!--文字离顶点的距离--> <attr name="ch_textSpace" format="dimension" /> <!--边距离--> <attr name="ch_padding" format="dimension" /> </declare-styleable> </resources> 复制代码
最后
完整代码如下:
public class RadarView extends View { private Context context; //线宽 private int lineWidth; //线颜色 private int boxlineColor; //内容颜色 private int valueColor; //文字颜色 private int textColor; //分割线颜色 private int cutlineColor; //文字大小 private int textSize; //文字离顶点的距离 private int textSpace; //几边形 private int sideSize; //辅助线 private int spaceCount; //边距 private int padding; //半径 private int radius; private Paint paint; private Canvas canvas; //中心x private int centerX; //中心y private int centerY; private List<Point> pointList; private List<String> labelText; private List<Double> labelValue; public void setLabelValue(List<Double> labelValue) { this.labelValue = labelValue; postInvalidate(); } public void setLabelText(List<String> labelText) { this.labelText = labelText; postInvalidate(); } public RadarView(Context context) { super(context); } public RadarView(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; init(attrs); } public RadarView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; init(attrs); } private void init(AttributeSet attrs) { TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RadarView); boxlineColor = array.getColor(R.styleable.RadarView_ch_boxlineColor, Color.BLACK); textColor = array.getColor(R.styleable.RadarView_ch_textColor, Color.BLACK); cutlineColor = array.getColor(R.styleable.RadarView_ch_cutlineColor, Color.MAGENTA); valueColor = array.getColor(R.styleable.RadarView_ch_valueColor, Color.MAGENTA); lineWidth = array.getDimensionPixelSize(R.styleable.RadarView_ch_lineWidth, 5); textSize = array.getDimensionPixelSize(R.styleable.RadarView_ch_textSize, 14); sideSize = array.getInt(R.styleable.RadarView_ch_sideSize, 6); spaceCount = array.getInt(R.styleable.RadarView_ch_spaceCount, 4); textSpace = array.getDimensionPixelSize(R.styleable.RadarView_ch_textSpace, 100); padding = array.getDimensionPixelSize(R.styleable.RadarView_ch_padding, 200); array.recycle(); paint = new Paint(); paint.setColor(boxlineColor); paint.setStrokeWidth(lineWidth); paint.setStyle(Paint.Style.STROKE); paint.setTextSize(textSize); paint.setTextAlign(Paint.Align.CENTER); if (radius == 0) { radius = Math.min(getScreenHeight(), getScreenWidth()) / 2 - padding; } pointList = new ArrayList<>(); centerX = getScreenWidth() / 2; centerY = getScreenHeight() / 2; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); this.canvas = canvas; Point sPoint = new Point(centerX, centerY - radius); Paint painte = new Paint(); painte.setStrokeWidth(lineWidth); painte.setColor(cutlineColor); painte.setStyle(Paint.Style.STROKE); painte.setPathEffect(new DashPathEffect(new float[]{10, 10}, 0)); pointList.clear(); for (int i = 0; i < sideSize; i++) { Point point = nextPoint(sPoint, -degree2radian() * i, radius); Path path = new Path(); path.moveTo(centerX, centerY); path.lineTo(point.x, point.y); //绘制分割线 canvas.drawPath(path, painte); pointList.add(point); } for (int i = 0; i < pointList.size(); i++) { if (labelText != null && labelText.size() > 0) { //绘制顶点文字 drawTextTop(pointList.get(i), labelText.get(i)); } } for (int i = 0; i < spaceCount; i++) { int radiu = radius / spaceCount * (i + 1); Path p = makePath(new Point(centerX, centerY - radiu), sideSize, radiu); paint.setColor(boxlineColor); canvas.drawPath(p, paint); } //绘制值 if (labelValue == null || labelValue.size() == 0) { return; } Path valuePath = makePath(radius, labelValue); paint.setColor(valueColor); paint.setStyle(Paint.Style.FILL); canvas.drawPath(valuePath, paint); } /** * 绘制顶点文字 * * @param point * @param text */ private void drawTextTop(Point point, String text) { Rect rect = new Rect(); paint.getTextBounds(text, 0, text.length(), rect); int x; int y; //偏移处理 if (point.x - centerX == 0 || Math.abs(point.x - centerX) < 5) { x = point.x; } else if (point.x - centerX > textSpace) { x = point.x + textSpace; } else { x = point.x - textSpace; } if (point.y - centerY == 0 || Math.abs(point.y - centerY) < 5) { y = point.y; } else if (point.y - centerY > textSpace) { y = point.y + textSpace; } else { y = point.y - textSpace; } paint.setStyle(Paint.Style.FILL); paint.setColor(textColor); canvas.drawText(text, x, y + (rect.bottom - rect.top) / 2, paint); paint.setStyle(Paint.Style.STROKE); } /** * 返回边框的path * * @param sPoint * @param count * @param radius * @return */ private Path makePath(Point sPoint, int count, int radius) { Path path = new Path(); path.moveTo(sPoint.x, sPoint.y); for (int i = 1; i < count; i++) { Point point = nextPoint(sPoint, -degree2radian() * i, radius); path.lineTo(point.x, point.y); } path.close(); return path; } private Path makePath(int radius, List<Double> values) { Path path = new Path(); Point sPoint = new Point(centerX, (int) (centerY - radius * values.get(0))); path.moveTo(sPoint.x, sPoint.y); for (int i = 1; i < values.size(); i++) { sPoint = new Point(centerX, (int) (centerY - radius * values.get(i))); Point point = nextPoint(sPoint, -degree2radian() * i, (int) (radius * values.get(i))); path.lineTo(point.x, point.y); Log.e("cheng", point.toString()); } path.close(); return path; } /** * 获取屏幕宽度 * * @return */ private int getScreenWidth() { Resources resources = getResources(); DisplayMetrics dm = resources.getDisplayMetrics(); return dm.widthPixels; } /** * 获取屏幕高度 * * @return */ private int getScreenHeight() { Resources resources = getResources(); DisplayMetrics dm = resources.getDisplayMetrics(); return dm.heightPixels; } /** * 角度制转弧度制 */ private double degree2radian() { return 2 * Math.PI / sideSize; } // arc为弧度,在顶点处建立直角坐标系,用r和arc确定下一个点的坐标 public Point nextPoint(Point point, double arc) { Point p = new Point(); p.x = (int) (point.x - radius * Math.sin(arc)); p.y = (int) (point.y + radius - radius * Math.cos(arc)); return p; } /** * arc为弧度,在顶点处建立直角坐标系,用r和arc确定下一个点的坐标 */ public Point nextPoint(Point point, double arc, int radius) { Point p = new Point(); p.x = (int) (point.x - radius * Math.sin(arc)); p.y = (int) (point.y + radius - radius * Math.cos(arc)); return p; } } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
编程珠玑(续)(修订版)
【美】Jon Bentley 乔恩•本特利 / 钱丽艳、刘田 / 人民邮电出版社 / 2015-2 / CNY 35.00
历史上最伟大的计算机科学著作之一 融深邃思想、实战技术与趣味轶事于一炉的奇书 带你真正领略计算机科学之美 多年以来,当程序员们推选出最心爱的计算机图书时,《编程珠玑》总是位于前列。正如自然界里珍珠出自细沙对牡蛎的磨砺,计算机科学大师Jon Bentley以其独有的洞察力和创造力,从磨砺程序员的实际问题中凝结出一篇篇不朽的编程“珠玑”,成为世界计算机界名刊《ACM通讯》历史上最受欢......一起来看看 《编程珠玑(续)(修订版)》 这本书的介绍吧!