最近一直在學習Flutter
,感受還不錯,可是Android
也不能拉下,回顧下前3篇的內容,讓咱們一塊兒畫個雷達圖吧。java
先看效果圖 canvas
須要解決的問題ide
首先,咱們以屏幕中心點O ( centerX, centerY
)正上方的點A爲起始點,則其座標爲centerX, centerY - radius
, 則B點的座標應該爲centerX - radius * Math.sin(∠AOB)
,centerY + radius - radius * Math.cos(∠AOB)
post
/** * 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篇(二)this
/** * 返回邊框的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
來設置spa
Paint painte = new Paint();
painte.setStrokeWidth(lineWidth);
painte.setColor(cutlineColor);
painte.setStyle(Paint.Style.STROKE);
painte.setPathEffect(new DashPathEffect(new float[]{10, 10}, 0));
複製代碼
繪製的話,須要將上一步計算的座標,每一個和中心點相連便可code
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);
}
複製代碼
間隔線的處理跟邊框的繪製類似,只是傳入的半徑不一樣orm
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);
}
複製代碼
文字的位置計算,須要用到上一步保存的頂點座標cdn
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;
}
}
複製代碼