效果图预览
1. 分析
1. 绘制多边形
2. 连接多边形各顶点
3. 绘制多边形各个顶点的文字
4. 绘制每一块的刻度
5. 造一些假数据用于绘制数据区域
2. 技术实现原理
1. PathMeasure可以获取任意正多边形各个坐标的余弦值、正弦值
2. 连线闭合采用Path类实现
3. 绘制文字采用canvas自带的canvas.drawText方法 不过需要计算角度
4. 绘制刻度需要计算各个点的坐标 可以用PointF记录点的坐标
5. 文本内容的水平垂直居中处理
3. 初始化一些东西
SIDE_NUM代表边的数目即几边形,PathMeasure mMeasure的
mMeasure.setPath(mPath,true)方法为PathMeasure设置路径,
boolean getPosTan (float distance, float[] pos, float[] tan)
- distance为距离当前path起点的距离,取值范围为0到path的长度。
- pos 如果不为null,则返回path当前距离的位置坐标,pos[0] = x,pos[1] = y 。
- tan 如果不为null,则返回当前位置坐标的切线,tan[0] = x, tan[1] = y 。
- 返回值为boolean,true表示成功,数据会存入pas、tan,反之则为失败,数据也不会存入pas、tan。
mCosArray,mCosArray用来存放各个顶点的余弦和正弦值
//定义多边形顶点文字
private String[] mStrings = {“A”,”B”,”C”,”D”,”E”,”F”,”G”,”H”};
//定义多边形数据区域x坐标的数据
private float[] mValues = {10.0f,47.0f,11.0f,38.0f,9.0f,52.0f,14.0f,37.0f};
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2;
mCenterY = h / 2;
mPointF = new PointF();
mLength = DensityUtil.dip2px(getContext(),120);
mPath.addCircle(0,0,mLength, Path.Direction.CW);
mMeasure.setPath(mPath,true);
mPos = new float[2];
mTan = new float[2];
mCosArray = new float[SIDE_NUM];
mSinArray = new float[SIDE_NUM];
for (int i=0; i < SIDE_NUM; i++){
mMeasure.getPosTan((float) (Math.PI * 2 * mLength * i / SIDE_NUM), mPos, mTan);
mCosArray[i] = mTan[0];
mSinArray[i] = mTan[1];
}
mPath.reset();
}
4. 绘制多边形和多边形连线
a. 绘制多边形
mPath.lineTo连接各个顶点 顶点坐标值=mLength*正弦和余弦值
POLYGON_NUM表示画多少个多边形 多边形从大往小画的
所以canvas.scale(1 - i / POLYGON_NUM,1 - i / POLYGON_NUM);这个代表缩放比例,每次画完一个多边形都需要闭合和重置mPath.close()和mPath.reset();
canvas.save()和canvas.restore()是保证这一次绘制过程对别的绘制不会有影响,因为这里操作了scale比例缩放
//绘制多边形
private void drawPolygon(Canvas canvas) {
mPaint.setColor(Color.GRAY);
for (int i = 0; i < POLYGON_NUM; i++){
canvas.save();
canvas.scale(1 - i / POLYGON_NUM,1 - i / POLYGON_NUM);
mPath.moveTo(0,mLength);
for (int j = 0; j < SIDE_NUM; j++){
mPath.lineTo(mLength* mCosArray[j],
mLength* mSinArray[j]);
}
mPath.close();
canvas.drawPath(mPath,mPaint);
mPath.reset();
canvas.restore();
}
}
b. 多边形连线
mPaint.setColor(Color.BLUE);加颜色区分
mPath.moveTo(0,0);每次都移动坐标点到原点
原点开始连接各个顶点
mPath.lineTo(mLength * mCosArray[j],mLength * mSinArray[j]);
//绘制多边形的连线
private void drawPolygonLine(Canvas canvas) {
mPaint.setColor(Color.BLUE);
//连线
for (int j=0; j< SIDE_NUM; j++){
mPath.moveTo(0,0);
mPath.lineTo(mLength * mCosArray[j],
mLength * mSinArray[j]);
drawPolygonText(canvas,j);
}
mPath.close();
canvas.drawPath(mPath,mPaint);
mPath.reset();
}
5. 绘制多边形各顶点文字
canvas.rotate(180);旋转180的原因是我是从底部90度开始的,为了保证A-F顺时针旋转,所以旋转180度,根据cos值判断文字的方向 不一定说要以0.2判断 我不过取了一个相对值
//绘制多边形各顶点文字
private void drawPolygonText(Canvas canvas,int index) {
canvas.save();
canvas.rotate(180);
mPointF.x = -mLength * mCosArray[index] * 1.1f;
mPointF.y = -mLength * mSinArray[index] * 1.1f;
//根据cos值,判断文字位置,设置居左、居中、居右
if (mCosArray[index] > 0.2){
textCenter(new String[]{mStrings[index]},mTextPaint,canvas,mPointF, Paint.Align.RIGHT);
}else if (mCosArray[index] < -0.2){
textCenter(new String[]{mStrings[index]},mTextPaint,canvas,mPointF, Paint.Align.LEFT);
}else {
textCenter(new String[]{mStrings[index]},mTextPaint,canvas,mPointF, Paint.Align.CENTER);
}
canvas.restore();
}
/**
* 多行文本居中、居右、居左
* @param strings 文本字符串列表
* @param point 点的坐标
*/
protected void textCenter(String[] strings, Paint paint, Canvas canvas, PointF point, Paint.Align align){
paint.setTextAlign(align);
Paint.FontMetrics fontMetrics= paint.getFontMetrics();
float top = fontMetrics.top;
float bottom = fontMetrics.bottom;
float ascent = fontMetrics.ascent;
float descent = fontMetrics.descent;
int length = strings.length;
float total = (length - 1) * (-top + bottom) + (-ascent + descent);
float offset = total / 2 - bottom;
for (int i = 0; i < length; i++) {
float yAxis = -(length - i - 1) * (-top + bottom) + offset;
canvas.drawText(strings[i], point.x, point.y + yAxis, paint);
}
}
6. 绘制刻度
mPointF.x: 计算每一个多边形顶点处的X坐标
mPointF.y:计算每一个多边形顶点处的Y坐标
//绘制刻度
private void drawPolygonScale(Canvas canvas) {
canvas.save();
canvas.rotate(180);
DecimalFormat sdf = new DecimalFormat ("0");
//外循环控制边数 每一个顶点处都绘制刻度
for (int i = 0; i < SIDE_NUM; i++) {
//内循环控制多边形数量
for (int j = 1; j < POLYGON_NUM; j++){
mPointF.x = mLength * (1 - j / POLYGON_NUM) * mCosArray[i];
mPointF.y = mLength * (1 - j / POLYGON_NUM) * mSinArray[i];
String text = sdf.format(10 * (POLYGON_NUM - j));
mScaleTextPaint.getTextBounds(text,0,text.length(),mScaleTextRect);
canvas.drawText(text, mPointF.x - mScaleTextRect.width() / 2, mPointF.y + mScaleTextRect.height() / 2, mScaleTextPaint);
}
}
//绘制中心点数字
String centerValue = "0";
mScaleTextPaint.getTextBounds(centerValue,0,centerValue.length(),mScaleTextRect);
canvas.drawText(centerValue,0 - mScaleTextRect.width() / 2,0 + mScaleTextRect.height() / 2,mScaleTextPaint);
canvas.restore();
}
7. 绘制数据区域
mValues定义一个x轴的数据点集合,然后根据一定比例计算相应的y轴的数据点集合,然后通过path连接各个坐标点
//绘制数据区域 连接各个数据点
private void drawPolygonData(Canvas canvas) {
for (int i = 0; i < SIDE_NUM; i++){
float value = mValues[i];
float yValue = value * 6.18f;
Log.e("drawGraph value",value+" ;yValue: "+yValue);
if ( i == 0 ){
mPath.moveTo(yValue * mCosArray[i], yValue * mSinArray[i]);
}else {
mPath.lineTo(yValue * mCosArray[i], yValue*mSinArray[i]);
}
}
mPath.close();
canvas.drawPath(mPath, mDataFillPaint);
canvas.drawPath(mPath,mStrokePaint);
mPath.reset();
}
8. 小结和源码下载
小结:
最核心的是通过PathMeasure类的getPosTan方法获得此任意正多边形各角坐标的余弦值、正弦值,
然后通过正弦余弦值获得各个点的x,y坐标值,还有就是Path类的使用以及canvas的旋转,缩放等操作,
别忘记canvas.save和canvas.restore不然会影响其他的绘制
源码下载:
最后统一提供下载地址
9.联系方式
QQ:1509815887