1. Description
When customizing the clock component, the basic control used is mainly Canvas . There are two ways to draw related elements: one is to draw all component elements in the same canvas, which requires constant adjustment of the properties of the brush and canvas It is easy to be confused when saving and restoring; the other is to create multiple canvas components, and the elements of each part are drawn on their own canvases. The logic is relatively clear, but there will be relatively many canvas components. This article uses the second one. Way.
Show results:
2. Overall code
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()
}
}
}