1. 说明
在自定义时钟组件时,使用到的基本控件主要是Canvas,在绘制相关元素时有两种方式:一种时在同一个canvas中绘制所有的部件元素,这样需要不断的对画笔和画布的属性进行保存和恢复,容易混乱;另一种就是创建多个canvas组件,每一部分的元素绘制都在各自的画布进行绘制,逻辑比较清晰,但是canvas组件会相对较多,本文使用的是第二种方式。
效果展示:
2. 整体代码
import QtQuick 2.15
import QtQuick.Controls 2.15
Item{
id:root
implicitWidth: 400
implicitHeight: implicitWidth
// 尺寸属性
property real outerCircleRadius:root.width / 2.05
property real innerCircleRadius:root.width / 2.05
// 颜色属性
property color bgColor:"white"
property color outerColor:"black"
property color innerColor:"black"
property color innerRootColor:"lightSlateGray"
property color innerLineColorL:"#484D58"
property color innerLineColorS:"#63677A"
property color textColor:"black"
property color hourLineColor:"#484D58"
property color minuteLineColor:"#484D58"
property color secondLineColor:"red"
property color timeTxtColor:"black"
// 时间属性
property var hours
property var minutes
property var seconds
property var currentTime
property alias hoursAngle:hourLine.angle
property alias minutesAngle:minuteLine.angle
property alias secondsAngle:secondLine.angle
// 组件加载完成后先初始化当前时间
Component.onCompleted: {
calculateAngle()
}
// 时间计算
function calculateAngle(){
var date = new Date()
hours = date.getHours()
// 模除得到12小时制的小时数
hours = hours % 12
minutes = date.getMinutes()
seconds = date.getUTCSeconds()
currentTime = hours + ":" + minutes + ":" + seconds
hoursAngle = Math.PI*2/12*hours+Math.PI*2*minutes/12/60-Math.PI
minutesAngle = Math.PI*2*minutes/60 + Math.PI*2*seconds/60/60 -Math.PI
secondsAngle = Math.PI*2*seconds/60-Math.PI
}
// 绘制背景
Canvas{
id:bgCircle
width: root.width
height: root.height
anchors.centerIn: parent
onPaint: {
// 绘制背景
var ctx = getContext("2d")
ctx.save()
ctx.lineWidth = root.width/50
ctx.fillStyle = bgColor
ctx.beginPath()
ctx.arc(root.width/2,root.height/2,outerCircleRadius,0,Math.PI * (12/6))
ctx.fill()
ctx.restore()
}
}
// 绘制圆环轮廓
Canvas{
id:outerCircle
width: root.width
height: root.height
anchors.centerIn: parent
onPaint: {
var ctx = getContext("2d") //创建画师
//为画师创建画笔并设置画笔属性
ctx.lineWidth = root.width/50 //设置画笔粗细
ctx.strokeStyle = outerColor //设置画笔颜色
ctx.beginPath() //每次绘制调用此函数,重新设置一个路径
// 按照钟表刻度进行划分,一圈是Math.PI * 2,分成12个刻度,每个刻度占用 1/6
// canvas绘制圆弧,是按照顺时针绘制,起点默认在三点钟方向
ctx.arc(root.width/2,root.height/2,outerCircleRadius,0,Math.PI * (12/6))
ctx.stroke() //根据strokeStyle对边框进行描绘
}
}
// 绘制圆环内衬
Canvas{
id:innerCircle
width: root.width
height: root.height
anchors.centerIn: parent
property real endAngle:Math.PI * (12/6)
onPaint: {
var ctx = getContext("2d")
ctx.save()
ctx.lineWidth = root.width/50
ctx.strokeStyle = innerColor
ctx.beginPath()
ctx.arc(root.width/2,root.height/2,innerCircleRadius,0,endAngle)
ctx.stroke()
ctx.restore()
// 绘制指针根部圆圈
ctx.save()
ctx.lineWidth = root.width/50
ctx.fillStyle = innerRootColor
ctx.beginPath()
ctx.arc(root.width/2,root.height/2,innerCircleRadius/16,0,endAngle)
ctx.fill()
ctx.restore()
}
}
// 绘制刻度线
Canvas{
id:innerLine
width: root.width
height: root.height
anchors.centerIn: parent
property real lineNums:60
onPaint: {
var ctx = getContext("2d")
for (var i = 0; i <= lineNums; ++i){
ctx.beginPath();
var angle = 2 * Math.PI / 60 * i;
var dx = Math.cos(angle)*(outerCircleRadius-15);
var dy = Math.sin(angle)*(outerCircleRadius-15);
var dx2 = Math.cos(angle)*(outerCircleRadius-7);
var dy2 = Math.sin(angle)*(outerCircleRadius-7);
if (i % 5 === 0){
ctx.lineWidth = root.width/100
ctx.strokeStyle = innerLineColorL
}else{
ctx.lineWidth = root.width/200
ctx.strokeStyle = innerLineColorS
}
ctx.moveTo(root.width/2+dx,root.height/2+dy);
ctx.lineTo(root.width/2+dx2,root.height/2+dy2);
ctx.stroke();
}
}
}
// 绘制数字
Canvas{
id:drawText
width: root.width
height: root.height
anchors.centerIn: parent
property var numbers : [1,2,3,4,5,6,7,8,9,10,11,12]
onPaint: {
var ctx = getContext("2d")
ctx.font = "18px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
for(var i = 0; i < 12; ++i)
{
ctx.fillStyle = textColor
var angle = 2 * Math.PI / 12 * numbers[i] - 3.14 / 2;
var dx = Math.cos(angle)*(outerCircleRadius-30);
var dy = Math.sin(angle)*(outerCircleRadius-30);
ctx.fillText(numbers[i],root.width/2 + dx,root.height / 2 + dy);
ctx.fill()
}
}
}
// 绘制时针线
Canvas{
id:hourLine
width: root.width
height: root.height
anchors.centerIn: parent
property real angle
onPaint: {
var ctx = getContext("2d")
// 先清空画布上之前的内容
ctx.clearRect(0,0,width,height)
ctx.save()
ctx.beginPath()
ctx.lineWidth = root.width/100
ctx.strokeStyle=hourLineColor
// 平移坐标点(注意:坐标系原点先平移,再旋转)
ctx.translate(root.width/2,root.height/2)
// 旋转坐标系
ctx.rotate(angle)
// 坐标原点变化之后再进行实际的绘图
ctx.moveTo(0,-20);
ctx.lineTo(0,outerCircleRadius / 2 - 15);
ctx.stroke()
ctx.restore()
}
}
// 绘制分针线
Canvas{
id:minuteLine
width: root.width
height: root.height
anchors.centerIn: parent
property real angle
onPaint: {
var ctx = getContext("2d")
ctx.clearRect(0,0,width,height)
ctx.save()
ctx.beginPath()
ctx.lineWidth = root.width/100
ctx.strokeStyle=minuteLineColor
// 平移坐标点(注意:坐标系原点先平移,再旋转)
ctx.translate(root.width/2,root.height/2)
// 旋转坐标系
ctx.rotate(angle)
// 坐标原点变化之后再进行实际的绘图
ctx.moveTo(0,-25);
ctx.lineTo(0,outerCircleRadius / 2 - 5);
ctx.stroke()
ctx.restore()
}
}
// 绘制秒针线
Canvas{
id:secondLine
width: root.width
height: root.height
anchors.centerIn: parent
property real angle
onPaint: {
var ctx = getContext("2d")
ctx.clearRect(0,0,width,height)
ctx.save()
ctx.beginPath()
ctx.lineWidth = root.width/100
ctx.strokeStyle=secondLineColor
// 平移坐标点(注意:坐标系原点先平移,再旋转)
ctx.translate(root.width/2,root.height/2)
// 旋转坐标系
ctx.rotate(angle)
// 坐标原点变化之后再进行实际的绘图
ctx.moveTo(0,-30);
ctx.lineTo(0,outerCircleRadius / 1.5);
ctx.stroke()
ctx.restore()
}
}
// 当前时间显示
Text{
id:timeTxt
y:root.height * 0.75
anchors.horizontalCenter: root.horizontalCenter
text: currentTime
font.pixelSize: root.width/12
color: timeTxtColor
}
// 使用定时器(每秒钟进行计算)
Timer{
id:angleCal
interval: 1000
repeat: true
running: true
onTriggered: {
calculateAngle()
hourLine.requestPaint()
minuteLine.requestPaint()
secondLine.requestPaint()
}
}
}