不得不说canvas确实很方便快捷。在绘制2D图形这快简直太方便,以前用QWidget绘制过,今天没事就想想用QML来绘制。
效果
先来看看绘制后的一个简单效果:
绘制步骤
- 创建画布
- 绘制中心圆
- 绘制文字(单位以及数字)
- 绘制刻度
- 绘制指针
- 绘制最外圈大圆
- 添加update接口,进行动态刷新显示
以上步骤中其中几项可以合并在一起的,但是为了清晰所以单独列出来进行绘制.
难点
绘制仪表盘难点在于刻度角度的计算,以及指针位置的计算。
开始绘制
进行绘制时,按照上述步骤挨个绘制即可,需要注意的就是坐标原点,为了便于绘制,开始时直接将画布原点进行了平移,移到了窗口中央。
相关数学知识
绘制常见的 2D 图像用的基本数学较多,还给老师的知识必须补补了。
三角函数
仪表盘上的刻度起始位置就是圆上的某个点,因此确定点的坐标很重要。
sinθ = AC / AO
cosθ = OC / AO
AO即为圆的半径,θ的值需要根据刻度间隔来确定。
圆心方程
(x-a)²+(y-b)²=r²,圆心O(a,b),半径r。
继而可以求出圆上任意一点的坐标了:
圆心:x0,y0
半径:r
角度:angle
圆上任意一点为:(x1,y1)
x1 = x0 + r * cos(angle)
y1 = y0 + r * sin(angle)
创建画布
Canvas
{
id: canvas
width: parent.width
height: parent.height
onPaint:
{
var ctx = getContext("2d");
drawSpeedPanel(ctx);
ctx.restore();
}
}
这样以后的绘制到会在函数drawSpeedPanel
中进行。
移动原点
var width = canvas.width
var height = canvas.height
//计算每一个刻度占的度数
var perAngle = 300/((m_speedNum.length - 1) * m_step);
ctx.clearRect(0,0,width,height);
ctx.save();
//将原点移到中心点,方便绘制
ctx.translate(width/2,height/2);
上述代码将坐标原点移到了窗口中心位置,在绘制结束后要恢复,要不然坐标会紊乱.
每次开始重绘之前先清空当前画布,然后再重绘!
绘制最中心圆
function drawCenterCircle(ctx)
{
ctx.beginPath();
var radial = ctx.createRadialGradient(0,0,m_radius*0.5,0,0,m_radius);
radial.addColorStop(0,m_colorCenter);
radial.addColorStop(1,"#6D8CA3");
ctx.fillStyle = radial;
ctx.arc(0,0,m_radius,0,Math.PI*2,false);
ctx.fill();
ctx.closePath();
}
上面创建了一个渐变填充,绘制圆后会进行填充。
绘制单位以及数值
//绘制单位以及速度值
function drawUnitText(ctx)
{
//文字大小根据圆的半径自动调整,这里因为canvas没有设置文本字体大小属性,所以只能用字符串拼接了
ctx.font = m_radius*1.2+ "px serif"
ctx.textBaseline = "middle";
ctx.fillStyle = m_textColor;
//绘制上面的单位km/h
ctx.fillText(m_unitName,-m_radius*1.2, -m_radius*2.4);
//绘制下面的速度80
ctx.fillText(m_value, -m_radius*0.6,m_radius*2.4);
}
为了使字体大小能基本进行适配,所以上面对字体大小做了小的处理。
绘制刻度
//绘制刻度
function drawScale(ctx,valueArry,perAngle)
{
var i = 0;
//计算数组长度
var length = valueArry.length - 1;
// ctx.rotate( rads(120) );
for ( var deg = 0; deg <= length*m_step; deg++)
{
ctx.beginPath();
var angle = -240 + perAngle*deg;
var spotX = m_scaleRadius*Math.cos(rads(angle))
var spotY = m_scaleRadius*Math.sin(rads(angle))
var spot = m_radius *0.36;
ctx.lineWidth = 1.2;
if ( deg >= (length - 4)*m_step)
{
ctx.fillStyle = "#E6F612"
ctx.strokeStyle = "#E6F612"
}
if ( deg >= (length - 2)*m_step)
{
ctx.fillStyle = "#CA0B14"
ctx.strokeStyle = "#CA0B14"
}
if ( 0 == deg %10)
{
ctx.lineWidth = 2.2
ctx.strokeStyle = m_textColor
spot = m_radius * 0.58
var textX = m_scaleRadius*1.24*Math.cos(rads(angle));
var textY = m_scaleRadius*1.24*Math.sin(rads(angle));
ctx.font = m_radius*0.68+"px Arial";
ctx.textBaseline = "middle";// 文字垂直对齐方式
ctx.textAlign = "center"; // 文字水平对齐方式
ctx.fillText(valueArry[i],textX,textY);
i++
}
if ( (0 == deg%5) && (0 != deg%10))
{
ctx.lineWidth = 1.6
spot = m_radius * 0.48
}
var endX = (m_scaleRadius + spot )*Math.cos(rads(angle));
var endY = (m_scaleRadius + spot )*Math.sin(rads(angle));
ctx.moveTo(spotX,spotY);
ctx.lineTo(endX,endY);
ctx.stroke();
ctx.closePath();
}
}
绘制刻度属于中重点,主要是计算了绘制的坐标!因为不是360度进行绘制,只绘制300度,所以需要进行计算。
绘制指针
//画指针
function drawPointer(ctx,curValue,perAngle)
{
//计算当前的value所处的角度
var transAngle = curValue*perAngle;
console.log("transAngle:"+transAngle)
console.log("perAngle:"+perAngle)
ctx.save();
ctx.beginPath();
ctx.rotate(rads(120 + transAngle));
// ctx.globalAlpha = 0.5
var gradient = ctx.createRadialGradient(0, 0, m_radius, 0, 0, m_pointerRadius*1.2);
gradient.addColorStop(0, 'rgba(16,110,180,0.6)');
gradient.addColorStop(0.5, 'rgba(21,165,174,0.8)');
gradient.addColorStop(1,'rgba(30,130,139,1)');
ctx.fillStyle = gradient;
//设定指针夹角大小(这里是角度),这里可以根据需要进行调整
var angle = 40;
//计算圆上点的坐标
var point1 = getCircleCoordinate(m_radius,0,0,angle);
var point2 = [m_pointerRadius,0];
ctx.moveTo(point1[0],point1[1]);
ctx.arc(0,0,m_radius,rads(angle),rads(-angle));
ctx.lineTo(point2[0],point2[1]);
ctx.lineTo(point1[0],point1[1]);
ctx.fill();
ctx.closePath();
ctx.restore();
}
这里就用到了计算圆上任意点的坐标了
已知圆心(a,b),半径:r,求圆上某一点的坐标(x,y):
(x-a)*(x-a) + (y-b)*(y-b) = r*r
/*
求圆上任意一点的坐标
已知圆的半径,圆心坐标,以及对应的角度
*/
function getCircleCoordinate(radius,x,y,angle)
{
var point=[];
var potX = x + radius * Math.cos( rads(angle));
var potY = y + radius * Math.sin( rads (angle));
point.push(potX);
point.push(potY);
return point;
}
绘制最外圈大圆
//画最外面的大圆
function drawOutCircle(ctx)
{
ctx.beginPath();
var radial = ctx.createRadialGradient(0,0,m_radius*7,0,0,m_radius*8.2);
radial.addColorStop(0,"#244461");
radial.addColorStop(0.5,"#265882");
radial.addColorStop(1,"#2D628A");
ctx.fillStyle = radial;
ctx.arc(0,0,m_radius*7,0,Math.PI*2,false);
ctx.arc(0,0,m_radius*8.2,Math.PI*2,false);
ctx.fill();
ctx.closePath();
}
最外圈大圆其实就是绘制了2个同心圆,然后进行渐变填充。效果不是很好,还可以继续美化处理。
最终的效果就是这个样子了:
测试
使用android套件编译下,手机上的效果如下:
目前没有做分辨率适配,很明显,刻度数字太大了,完了得添加适配进去.
参考文章
作者:鹅卵石
时间: 2018年1月21日9:55:47
版本:V 0.0.1
邮箱:kevinlq@yeah.net
版权:本博客若无特别声明,均属于作者原创文章,欢迎大家转载分享。但是,
希望您注明来源,并留下原文地址,这是对作者最大的尊重,也是最知识的尊重。
如果您对本文有任何问题,可以在下方留言,或者Email我.